├── .csharpierignore ├── .csharpierrc.yaml ├── src ├── Altinn.Codelists │ ├── GlobalUsing.cs │ ├── SSB │ │ ├── readme.md │ │ ├── Clients │ │ │ ├── ClassificationSettings.cs │ │ │ ├── ClassificationCodes.cs │ │ │ ├── ClassificationCode.cs │ │ │ ├── ClassificationsHttpClientCached.cs │ │ │ └── ClassificationsHttpClient.cs │ │ ├── IClassificationsClient.cs │ │ ├── Models │ │ │ └── Classification.cs │ │ ├── ClassificationOptions.cs │ │ └── Extensions │ │ │ └── ServiceCollectionExtensions.cs │ ├── RestCountries │ │ ├── Data │ │ │ └── Resources.cs │ │ ├── readme.md │ │ ├── Models │ │ │ ├── Filter.cs │ │ │ ├── Name.cs │ │ │ ├── Currency.cs │ │ │ └── Country.cs │ │ ├── ICountryClient.cs │ │ ├── Extensions │ │ │ └── ServiceCollectionExtensions.cs │ │ └── Clients │ │ │ └── CountriesClient.cs │ ├── Posten │ │ ├── IPostalCodesClient.cs │ │ ├── PostenSettings.cs │ │ ├── Clients │ │ │ ├── PostalCodesHttpClient.cs │ │ │ ├── PostalCodesCsvParser.cs │ │ │ └── PostalCodeRecord.cs │ │ ├── PostalCodesCodelistsProvider.cs │ │ └── Extensions │ │ │ └── ServiceCollectionExtensions.cs │ ├── Kartverket │ │ └── AdministrativeUnits │ │ │ ├── AdministrativeUnitsSettings.cs │ │ │ ├── IAdministrativeUnitsClient.cs │ │ │ ├── Models │ │ │ ├── County.cs │ │ │ └── Municipality.cs │ │ │ ├── CountiesCodelistProvider.cs │ │ │ ├── MunicipalitiesCodelistProvider.cs │ │ │ ├── Extensions │ │ │ └── ServiceCollectionExtensions.cs │ │ │ └── Clients │ │ │ ├── AdministrativeUnitsHttpClient.cs │ │ │ └── AdministrativeUnitsHttpClientCached.cs │ ├── Extensions │ │ ├── CollectionExtensions.cs │ │ └── ServiceCollectionExtensions.cs │ ├── Altinn.Codelists.csproj │ └── Utilities │ │ ├── PredicateBuilder.cs │ │ └── EmbeddedResource.cs ├── Directory.Build.targets └── Directory.Build.props ├── test └── Altinn.Codelists.Tests │ ├── RestCountries │ ├── restCountries.http │ └── Clients │ │ └── CountriesClientTests.cs │ ├── Kartverket │ └── AdministrativeUnits │ │ ├── Testdata │ │ ├── fylke46_kommuner.json │ │ ├── administrativeUnits.http │ │ └── fylker.json │ │ ├── Extensions │ │ └── ServiceCollectionTests.cs │ │ ├── CountiesCodelistProviderTests.cs │ │ ├── Clients │ │ ├── AdministrativeUnitsClientTests.cs │ │ └── CountiesClientCachedTests.cs │ │ ├── MunicipalitiesCodelistProviderTests.cs │ │ └── Mocks │ │ └── AdministrativeUnitsHttpClientMock.cs │ ├── ModuleInitializer.cs │ ├── GlobalUsing.cs │ ├── SSB │ ├── Testdata │ │ ├── sex.json │ │ ├── units.json │ │ ├── classifications.http │ │ ├── smallGame_Variant.json │ │ ├── maritalStatus.json │ │ ├── counties.json │ │ └── baseAmountNationalInsurance.json │ ├── CountiesCodelistProviderTests.cs │ ├── CountriesCodelistProviderTests.cs │ ├── MaritalStatusCodelistProviderTests.cs │ ├── MunicipalitiesCodelistProviderTests.cs │ ├── Extensions │ │ └── ExtensionTests.cs │ ├── BaseAmountNationalInsuranceCodelistProviderTests.cs │ ├── UnitsCodelistProviderTests.cs │ ├── SmallGameVariantTests.cs │ ├── Clients │ │ ├── ClassificationsHttpClientTests.cs │ │ └── ClassificationsHttpClientCachedTests.cs │ ├── SexCodelistProviderTests.cs │ ├── ClassificationOptionsTests.cs │ ├── OccupationsCodelistProviderTests.cs │ ├── IndustryGroupingCodelistProviderTests.cs │ └── Mocks │ │ └── ClassificationsHttpClientMock.cs │ ├── PublicApiTests.cs │ ├── EmbeddedResource.cs │ ├── Posten │ ├── Clients │ │ ├── PostalCodesHttpClientTests.cs │ │ ├── PostenFixture.cs │ │ └── mappings │ │ │ └── wiremock_GET_postnummerregister-ansi.txt.json │ └── PostalCodesCodelistProviderTests.cs │ ├── EnsureNoCodelistIdCollisions.cs │ └── Altinn.Codelists.Tests.csproj ├── global.json ├── renovate.json ├── .config └── dotnet-tools.json ├── .github ├── release.yml └── workflows │ ├── dotnet-test.yml │ ├── publish-release.yml │ ├── codeql.yml │ ├── test-and-analyze-fork.yml │ └── test-and-analyze.yml ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── Directory.Packages.props ├── Directory.Build.props ├── Altinn.Codelists.sln ├── .gitattributes ├── .gitignore ├── .editorconfig └── README.md /.csharpierignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .git/ 4 | *.xml 5 | *.csproj 6 | *.props 7 | *.targets 8 | -------------------------------------------------------------------------------- /.csharpierrc.yaml: -------------------------------------------------------------------------------- 1 | printWidth: 120 2 | useTabs: false 3 | tabWidth: 4 4 | endOfLine: auto 5 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/GlobalUsing.cs: -------------------------------------------------------------------------------- 1 | global using System.Text; 2 | global using System.Text.Json; 3 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/RestCountries/restCountries.http: -------------------------------------------------------------------------------- 1 | # Response is saved raw into countries.json 2 | GET https://restcountries.com/v3.1/all 3 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/readme.md: -------------------------------------------------------------------------------- 1 | # SSB 2 | Various codelists provided by SSB public api's. 3 | 4 | Source: https://data.ssb.no/api/klass/v1/api-guide.html 5 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.416", 4 | "rollForward": "latestFeature", 5 | "allowPrerelease": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>Altinn/renovate-config" 5 | ], 6 | "ignoreDeps": ["Altinn.App.Core"] 7 | } 8 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Testdata/fylke46_kommuner.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/codelists-lib-dotnet/main/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Testdata/fylke46_kommuner.json -------------------------------------------------------------------------------- /src/Altinn.Codelists/RestCountries/Data/Resources.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Codelists.RestCountries.Data; 2 | 3 | internal static class Resources 4 | { 5 | internal const string CountriesJson = "Altinn.Codelists.RestCountries.Data.countries.json"; 6 | } 7 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "csharpier": { 6 | "version": "1.2.1", 7 | "commands": [ 8 | "csharpier" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Altinn.Codelists.Tests; 4 | 5 | internal static class ModuleInitializer 6 | { 7 | [ModuleInitializer] 8 | public static void Initialize() 9 | { 10 | VerifierSettings.AutoVerify(includeBuildServer: false); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/GlobalUsing.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections.Generic; 3 | global using System.Linq; 4 | global using System.Net.Http; 5 | global using System.Threading; 6 | global using System.Threading.Tasks; 7 | global using Microsoft.Extensions.Options; 8 | global using Xunit; 9 | global using Xunit.Abstractions; 10 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Posten/IPostalCodesClient.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.Posten.Clients; 2 | 3 | namespace Altinn.Codelists.Posten; 4 | 5 | /// 6 | /// Client for getting the offical list of post codes in Norway. 7 | /// 8 | public interface IPostalCodesClient 9 | { 10 | /// 11 | /// Get all postal codes. 12 | /// 13 | /// 14 | Task> GetPostalCodes(); 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/Clients/ClassificationSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Codelists.SSB.Clients; 2 | 3 | /// 4 | /// Options to control the behavior of 5 | /// 6 | public class ClassificationSettings 7 | { 8 | /// 9 | /// Base url to the api endpoint for classifications. 10 | /// 11 | public string BaseApiUrl { get; set; } = "https://data.ssb.no/api/klass/v1/classifications/"; 12 | } 13 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/RestCountries/readme.md: -------------------------------------------------------------------------------- 1 | # REST Countries 2 | All countries of the world with various additional data, like: region, subregion, capital, currency, capital city etc. 3 | 4 | Source: https://restcountries.com/ 5 | Last updated: 09.07.2022 6 | 7 | ## Updates 8 | The json data is stored within the nuget package due to it's size and to ensure it's always available. 9 | The update frequency is naturaly low and is done by fetching the source and storing the response json 10 | in the data folder as countries.json. 11 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/Clients/ClassificationCodes.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Codelists.SSB.Clients; 4 | 5 | /// 6 | /// List of classification codes from a specified classification. 7 | /// 8 | public class ClassificationCodes 9 | { 10 | /// 11 | /// List of codes for a given classification. 12 | /// 13 | [JsonPropertyName("codes")] 14 | public List Codes { get; set; } = new List(); 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Kartverket/AdministrativeUnits/AdministrativeUnitsSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits; 2 | 3 | /// 4 | /// Options to control the behavior of 5 | /// 6 | public class AdministrativeUnitsSettings 7 | { 8 | /// 9 | /// Base url to the API endpoint for information on administrative units. 10 | /// 11 | public string BaseApiUrl { get; set; } = "https://ws.geonorge.no/kommuneinfo/v1/"; 12 | } 13 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | categories: 6 | - title: Breaking Changes 🛠 7 | labels: 8 | - semver-major 9 | - breaking-change 10 | - title: New Features 🎉 11 | labels: 12 | - semver-minor 13 | - feature 14 | - title: Bugfixes 🐛 15 | labels: 16 | - semver-patch 17 | - bugfix 18 | - title: Dependency Upgrades 📦 19 | labels: 20 | - dependency 21 | - title: Other Changes 22 | labels: 23 | - "*" 24 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/RestCountries/Models/Filter.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Codelists.RestCountries.Models; 2 | 3 | /// 4 | /// Class for filtering countries. 5 | /// 6 | public class Filter 7 | { 8 | /// 9 | /// Filter on region eg. Africa, Americas, Asia, Europe, Oceania 10 | /// 11 | public string? Region { get; set; } 12 | 13 | /// 14 | /// Filter on subregion eg. South America, Southern Europe, Central America, Eastern Asia, etc. 15 | /// 16 | public string? SubRegion { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Posten/PostenSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Codelists.Posten; 2 | 3 | /// 4 | /// Options to control the behavior of 5 | /// 6 | public class PostenSettings 7 | { 8 | internal static readonly string DefaultBaseUrl = "https://www.bring.no"; 9 | internal static readonly string DefaultPath = "/postnummerregister-ansi.txt"; 10 | 11 | /// 12 | /// URL to Brings postnummerregister API 13 | /// 14 | public string Url { get; set; } = $"{DefaultBaseUrl}{DefaultPath}"; 15 | } 16 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Testdata/administrativeUnits.http: -------------------------------------------------------------------------------- 1 | # Install Visual Studio Code Extension https://github.com/Huachao/vscode-restclient 2 | # to test the api used by the AdministrativeUnitsHttpClient 3 | # The json files commentet above the requests are stored as test data. 4 | 5 | ### 6 | # fylker.json 7 | GET https://ws.geonorge.no/kommuneinfo/v1/fylker 8 | 9 | ### 10 | # kommuner.json 11 | GET https://ws.geonorge.no/kommuneinfo/v1/kommuner 12 | 13 | ### 14 | # fylke46_kommuner.json 15 | GET https://ws.geonorge.no/kommuneinfo/v1/fylker/46?filtrer=kommuner,fylkesnavn,fylkesnummer 16 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/RestCountries/Models/Name.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Codelists.RestCountries.Models; 4 | 5 | /// 6 | /// Holds information about the name of a country. 7 | /// 8 | public class Name(string common, string official) 9 | { 10 | /// 11 | /// The common name 12 | /// 13 | [JsonPropertyName("common")] 14 | public string Common { get; set; } = common; 15 | 16 | /// 17 | /// The official name 18 | /// 19 | [JsonPropertyName("official")] 20 | public string Official { get; set; } = official; 21 | } 22 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/IClassificationsClient.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.SSB.Clients; 2 | 3 | namespace Altinn.Codelists.SSB; 4 | 5 | /// 6 | /// Client to get classification codes. 7 | /// 8 | public interface IClassificationsClient 9 | { 10 | /// 11 | /// Gets the codes for the specified classification. If no date is specified, the current date is used. 12 | /// 13 | Task GetClassificationCodes( 14 | int classificationId, 15 | string language = "nb", 16 | DateOnly? atDate = null, 17 | string level = "", 18 | string variant = "", 19 | string selectCodes = "" 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/RestCountries/ICountryClient.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.RestCountries.Models; 2 | 3 | namespace Altinn.Codelists.RestCountries; 4 | 5 | /// 6 | /// Information on all countries of the world. 7 | /// 8 | public interface ICountryClient 9 | { 10 | /// 11 | /// Get all the countries of the world. 12 | /// 13 | Task> GetCountries(); 14 | 15 | /// 16 | /// Get all countries matching the provided filters. 17 | /// Values within the same filter object are AND'ed, while 18 | /// values between filter objects are OR'ed. 19 | /// 20 | Task> GetCountries(IEnumerable filters); 21 | } 22 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/RestCountries/Models/Currency.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Codelists.RestCountries.Models; 4 | 5 | /// 6 | /// Holds information of a currency used within a country. 7 | /// 8 | public class Currency(string name, string symbol) 9 | { 10 | /// 11 | /// The name of the curreny eg. Norwegian krone, United States dollar, Pound sterling 12 | /// 13 | [JsonPropertyName("name")] 14 | public string Name { get; set; } = name; 15 | 16 | /// 17 | /// The symbol used to identify the currency eg. kr, $, £ 18 | /// 19 | [JsonPropertyName("symbol")] 20 | public string Symbol { get; set; } = symbol; 21 | } 22 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/RestCountries/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.RestCountries.Clients; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Altinn.Codelists.RestCountries; 5 | 6 | /// 7 | /// Extends the . 8 | /// 9 | public static class ServiceCollectionExtensions 10 | { 11 | /// 12 | /// Registers the interface 13 | /// 14 | public static IServiceCollection AddRestCountriesClient(this IServiceCollection services) 15 | { 16 | services.AddHttpClient(); 17 | services.AddTransient(); 18 | 19 | return services; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Testdata/sex.json: -------------------------------------------------------------------------------- 1 | { 2 | "codes": [ 3 | { 4 | "code": "1", 5 | "parentCode": null, 6 | "level": "1", 7 | "name": "Mann", 8 | "shortName": "", 9 | "presentationName": "", 10 | "validFrom": null, 11 | "validTo": null, 12 | "validFromInRequestedRange": "2022-07-10", 13 | "validToInRequestedRange": null, 14 | "notes": "" 15 | }, 16 | { 17 | "code": "2", 18 | "parentCode": null, 19 | "level": "1", 20 | "name": "Kvinne", 21 | "shortName": "", 22 | "presentationName": "", 23 | "validFrom": null, 24 | "validTo": null, 25 | "validFromInRequestedRange": "2022-07-10", 26 | "validToInRequestedRange": null, 27 | "notes": "" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /src/Altinn.Codelists/Posten/Clients/PostalCodesHttpClient.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | 3 | namespace Altinn.Codelists.Posten.Clients; 4 | 5 | internal sealed class PostalCodesHttpClient(HttpClient _httpClient, IOptions _options) 6 | : IPostalCodesClient 7 | { 8 | private readonly Uri _uri = new(_options.Value.Url); 9 | 10 | public async Task> GetPostalCodes() 11 | { 12 | using var response = await _httpClient.GetAsync(_uri.ToString(), HttpCompletionOption.ResponseHeadersRead); 13 | await using var responseStream = await response.Content.ReadAsStreamAsync(); 14 | 15 | var parser = new PostalCodesCsvParser(responseStream); 16 | List result = await parser.Parse(); 17 | 18 | return result; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Extensions/ServiceCollectionTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Extensions; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Extensions; 6 | 7 | public class ServiceCollectionTests 8 | { 9 | [Fact] 10 | public void AddAdministrativeUnits_ShouldResolveAppOptionProviders() 11 | { 12 | var services = new ServiceCollection(); 13 | services.AddKartverketAdministrativeUnits(); 14 | var provider = services.BuildServiceProvider(); 15 | 16 | IEnumerable appOptionsServices = provider.GetServices(); 17 | 18 | Assert.Equal(2, appOptionsServices.Count()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Extensions/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Codelists.Extensions; 2 | 3 | /// 4 | /// A class which contains useful methods for processing collections. 5 | /// 6 | internal static class CollectionExtensions 7 | { 8 | /// 9 | /// Checks whether enumerable is null or empty. 10 | /// 11 | /// The type of the enumerable. 12 | /// The System.Collections.Generic.IEnumerable`1 to be checked. 13 | /// True if enumerable is null or empty, false otherwise. 14 | public static bool IsNullOrEmpty(this IEnumerable enumerable) 15 | { 16 | if (enumerable != null) 17 | { 18 | return !enumerable.Any(); 19 | } 20 | 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Extensions; 2 | using Altinn.Codelists.Posten; 3 | using Altinn.Codelists.SSB.Extensions; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Altinn.Codelists.Extensions; 7 | 8 | /// 9 | /// Extends the . 10 | /// 11 | public static class ServiceCollectionExtensions 12 | { 13 | /// 14 | /// Registers the services required to get support for all codelists. 15 | /// 16 | public static IServiceCollection AddAltinnCodelists(this IServiceCollection services) 17 | { 18 | services.AddKartverketAdministrativeUnits(); 19 | services.AddSSBClassifications(); 20 | services.AddPosten(); 21 | 22 | return services; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Kartverket/AdministrativeUnits/IAdministrativeUnitsClient.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models; 2 | 3 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits; 4 | 5 | /// 6 | /// Information on Norways offical administrative units for counties and municipalities. 7 | /// 8 | public interface IAdministrativeUnitsClient 9 | { 10 | /// 11 | /// Get all the counties of Norway. 12 | /// 13 | public Task> GetCounties(); 14 | 15 | /// 16 | /// Get all the counties of Norway. 17 | /// 18 | public Task> GetMunicipalities(); 19 | 20 | /// 21 | /// Get all the municipalities within the specified county. 22 | /// 23 | public Task> GetMunicipalities(string countyNumber); 24 | } 25 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Kartverket/AdministrativeUnits/Models/County.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Models; 4 | 5 | /// 6 | /// Holds information about a county (fylke). 7 | /// 8 | public class County(string number, string name) 9 | { 10 | /// 11 | /// Unique identification number for the county. 12 | /// 13 | [JsonPropertyName("fylkesnummer")] 14 | public string Number { get; set; } = number; 15 | 16 | /// 17 | /// The name of the county in Norwegian. 18 | /// 19 | [JsonPropertyName("fylkesnavn")] 20 | public string Name { get; set; } = name; 21 | 22 | /// 23 | /// List of municipalities within the county 24 | /// 25 | [JsonPropertyName("kommuner")] 26 | public List Municipalities { get; set; } = new List(); 27 | } 28 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/PublicApiTests.cs: -------------------------------------------------------------------------------- 1 | using PublicApiGenerator; 2 | 3 | namespace Altinn.Codelists.Tests; 4 | 5 | public class PublicApiTests 6 | { 7 | private static readonly string[] _excludedAttributes = 8 | [ 9 | "System.Runtime.CompilerServices.RefSafetyRulesAttribute", 10 | "System.Diagnostics.DebuggerNonUserCodeAttribute", 11 | "System.ComponentModel.EditorBrowsableAttribute", 12 | "System.Runtime.CompilerServices.InternalsVisibleToAttribute", 13 | ]; 14 | 15 | [Fact] 16 | public async Task PublicApi_ShouldNotChange_Unintentionally() 17 | { 18 | // Arrange 19 | var assembly = typeof(Altinn.Codelists.Extensions.ServiceCollectionExtensions).Assembly; 20 | 21 | // Act 22 | var publicApi = assembly.GeneratePublicApi(new ApiGeneratorOptions { ExcludeAttributes = _excludedAttributes }); 23 | 24 | // Assert 25 | await Verify(publicApi); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Kartverket/AdministrativeUnits/CountiesCodelistProvider.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.App.Core.Models; 3 | 4 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits; 5 | 6 | /// 7 | /// Provides a codelist for counties of Norway. 8 | /// 9 | internal sealed class CountiesCodelistProvider(IAdministrativeUnitsClient _countiesHttpClient) : IAppOptionsProvider 10 | { 11 | /// 12 | public string Id => "fylker-kv"; 13 | 14 | /// 15 | public async Task GetAppOptionsAsync(string? language, Dictionary keyValuePairs) 16 | { 17 | var counties = await _countiesHttpClient.GetCounties(); 18 | 19 | var appOptions = new AppOptions() 20 | { 21 | Options = counties.Select(x => new AppOption() { Value = x.Number, Label = x.Name }).ToList(), 22 | }; 23 | 24 | return appOptions; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Testdata/fylker.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fylkesnavn": "Oslo", 4 | "fylkesnummer": "03" 5 | }, 6 | { 7 | "fylkesnavn": "Rogaland", 8 | "fylkesnummer": "11" 9 | }, 10 | { 11 | "fylkesnavn": "Møre og Romsdal", 12 | "fylkesnummer": "15" 13 | }, 14 | { 15 | "fylkesnavn": "Nordland", 16 | "fylkesnummer": "18" 17 | }, 18 | { 19 | "fylkesnavn": "Viken", 20 | "fylkesnummer": "30" 21 | }, 22 | { 23 | "fylkesnavn": "Innlandet", 24 | "fylkesnummer": "34" 25 | }, 26 | { 27 | "fylkesnavn": "Vestfold og Telemark", 28 | "fylkesnummer": "38" 29 | }, 30 | { 31 | "fylkesnavn": "Agder", 32 | "fylkesnummer": "42" 33 | }, 34 | { 35 | "fylkesnavn": "Vestland", 36 | "fylkesnummer": "46" 37 | }, 38 | { 39 | "fylkesnavn": "Trøndelag", 40 | "fylkesnummer": "50" 41 | }, 42 | { 43 | "fylkesnavn": "Troms og Finnmark", 44 | "fylkesnummer": "54" 45 | } 46 | ] -------------------------------------------------------------------------------- /src/Altinn.Codelists/Kartverket/AdministrativeUnits/Models/Municipality.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Models; 4 | 5 | /// 6 | /// Holds information about a municipality (kommune). 7 | /// 8 | public class Municipality(string number, string name, string nameInNorwegian) 9 | { 10 | /// 11 | /// Unique identification number for the municipality. 12 | /// 13 | [JsonPropertyName("kommunenummer")] 14 | public string Number { get; set; } = number; 15 | 16 | /// 17 | /// The name of the municipality in Norwegian or Sami. 18 | /// 19 | [JsonPropertyName("kommunenavn")] 20 | public string Name { get; set; } = name; 21 | 22 | /// 23 | /// The name of the municipality in Norwegian. 24 | /// 25 | [JsonPropertyName("kommunenavnNorsk")] 26 | public string NameInNorwegian { get; set; } = nameInNorwegian; 27 | } 28 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/CountiesCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.Kartverket.AdministrativeUnits; 3 | using Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Mocks; 4 | 5 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits; 6 | 7 | public class CountiesCodelistProviderTests 8 | { 9 | [Fact] 10 | public async Task GetAppOptionsAsync_ShouldReturnListOfCounties() 11 | { 12 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock( 13 | Options.Create(new AdministrativeUnitsSettings()) 14 | ); 15 | IAppOptionsProvider appOptionsProvider = new CountiesCodelistProvider(administrativeUnitsHttpClientMock); 16 | 17 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 18 | 19 | Assert.NotNull(appOptions.Options); 20 | Assert.Equal(11, appOptions.Options.Count); 21 | Assert.Equal("Vestland", appOptions.Options.First(x => x.Value == "46").Label); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/CountiesCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.SSB.Models; 5 | using Altinn.Codelists.Tests.SSB.Mocks; 6 | 7 | namespace Altinn.Codelists.Tests.SSB; 8 | 9 | public class CountiesCodelistProviderTests 10 | { 11 | [Fact] 12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes() 13 | { 14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 16 | "fylker", 17 | Classification.Counties, 18 | httpClientMock 19 | ); 20 | 21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 22 | 23 | Assert.NotNull(appOptions.Options); 24 | Assert.Equal(12, appOptions.Options.Count); 25 | Assert.Equal("Vestland", appOptions.Options.First(x => x.Value == "46").Label); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/EmbeddedResource.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | 4 | namespace Altinn.Codelists.Tests; 5 | 6 | public static class EmbeddedResource 7 | { 8 | public static async Task LoadDataAsString(string resourceName) 9 | { 10 | var resourceStream = LoadDataAsStream(resourceName); 11 | 12 | using var reader = new StreamReader(resourceStream); 13 | string text = await reader.ReadToEndAsync(); 14 | 15 | return text; 16 | } 17 | 18 | public static Stream LoadDataAsStream(string resourceName) 19 | { 20 | var assembly = Assembly.GetExecutingAssembly(); 21 | Stream? resourceStream = assembly.GetManifestResourceStream(resourceName); 22 | 23 | if (resourceStream == null) 24 | { 25 | throw new InvalidOperationException( 26 | $"Unable to find resource {resourceName} embedded in assembly {assembly.FullName}." 27 | ); 28 | } 29 | 30 | resourceStream.Seek(0, SeekOrigin.Begin); 31 | 32 | return resourceStream; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/CountriesCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.SSB.Models; 5 | using Altinn.Codelists.Tests.SSB.Mocks; 6 | 7 | namespace Altinn.Codelists.Tests.SSB; 8 | 9 | public class CountriesStatusCodelistProviderTests 10 | { 11 | [Fact] 12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes() 13 | { 14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 16 | "land", 17 | Classification.Countries, 18 | httpClientMock 19 | ); 20 | 21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 22 | 23 | Assert.NotNull(appOptions.Options); 24 | Assert.Equal(252, appOptions.Options.Count); 25 | Assert.Equal("Norge", appOptions.Options.First(x => x.Value == "NOR").Label); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/test/Altinn.Codelists.Tests/bin/Debug/net6.0/Altinn.Codelists.Tests.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/test/Altinn.Codelists.Tests", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/MaritalStatusCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.SSB.Models; 5 | using Altinn.Codelists.Tests.SSB.Mocks; 6 | 7 | namespace Altinn.Codelists.Tests.SSB; 8 | 9 | public class MaritalStatusCodelistProviderTests 10 | { 11 | [Fact] 12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes() 13 | { 14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 16 | "maritalStatus", 17 | Classification.MaritalStatus, 18 | httpClientMock 19 | ); 20 | 21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 22 | 23 | Assert.NotNull(appOptions.Options); 24 | Assert.Equal(9, appOptions.Options.Count); 25 | Assert.Equal("Gift", appOptions.Options.First(x => x.Value == "2").Label); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/MunicipalitiesCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.SSB.Models; 5 | using Altinn.Codelists.Tests.SSB.Mocks; 6 | 7 | namespace Altinn.Codelists.Tests.SSB; 8 | 9 | public class MunicipalitiesCodelistProviderTests 10 | { 11 | [Fact] 12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes() 13 | { 14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 16 | "kommuner", 17 | Classification.Municipalities, 18 | httpClientMock 19 | ); 20 | 21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 22 | 23 | Assert.NotNull(appOptions.Options); 24 | Assert.Equal(357, appOptions.Options.Count); 25 | Assert.Equal("Sogndal", appOptions.Options.First(x => x.Value == "4640").Label); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Altinn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Extensions/ExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Extensions; 4 | using Altinn.Codelists.SSB.Models; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Altinn.Codelists.Tests.SSB.Extensions; 8 | 9 | public class ExtensionTests 10 | { 11 | [Fact] 12 | public void AddSSBClassifications() 13 | { 14 | IServiceCollection services = new ServiceCollection(); 15 | services.AddSSBClassificationCodelistProvider("sivilstand", Classification.MaritalStatus); 16 | services.AddSSBClassificationCodelistProvider("yrker", Classification.Occupations); 17 | 18 | IServiceProvider serviceProvider = services.BuildServiceProvider(); 19 | 20 | IEnumerable classificationsClients = 21 | serviceProvider.GetServices(); 22 | 23 | Assert.Single(classificationsClients); 24 | 25 | var optionsProviders = serviceProvider.GetServices(); 26 | Assert.Equal(2, optionsProviders.Count()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test on windows, macos and ubuntu 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | types: [opened, synchronize, reopened] 8 | workflow_dispatch: 9 | jobs: 10 | analyze: 11 | strategy: 12 | matrix: 13 | os: [macos-latest,windows-latest,ubuntu-latest] # TODO: Fix test to run on ubuntu-latest also 14 | name: Run dotnet build and test 15 | runs-on: ${{ matrix.os}} 16 | env: 17 | DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE: false 18 | steps: 19 | - name: Setup .NET 20 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 21 | with: 22 | dotnet-version: | 23 | 8.0.x 24 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 25 | with: 26 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 27 | - name: Build 28 | run: | 29 | dotnet build Altinn.Codelists.sln -v m 30 | - name: Test 31 | run: | 32 | dotnet test Altinn.Codelists.sln -v m 33 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/BaseAmountNationalInsuranceCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.SSB.Models; 5 | using Altinn.Codelists.Tests.SSB.Mocks; 6 | 7 | namespace Altinn.Codelists.Tests.SSB; 8 | 9 | public class BaseAmountNationalInsuranceCodelistProviderTests 10 | { 11 | [Fact] 12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes() 13 | { 14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 16 | "grunnbeløpfolketrygden", 17 | Classification.BaseAmountNationalInsurance, 18 | httpClientMock 19 | ); 20 | 21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 22 | 23 | Assert.NotNull(appOptions.Options); 24 | Assert.Equal(32, appOptions.Options.Count); 25 | Assert.Equal("111 477 kroner", appOptions.Options.First(x => x.Value == "2022").Label); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Posten/Clients/PostalCodesHttpClientTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.Posten; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Altinn.Codelists.Tests.Posten.Clients; 5 | 6 | public class PostalCodesHttpClientTests 7 | { 8 | [Fact] 9 | public async Task Get() 10 | { 11 | await using var fixture = await PostenFixture.Create(proxy: false); 12 | var server = fixture.Server; 13 | var baseUrl = server.Url; 14 | Assert.NotNull(baseUrl); 15 | var baseUri = new Uri(baseUrl); 16 | var uri = new Uri(baseUri, PostenSettings.DefaultPath); 17 | 18 | var services = new ServiceCollection(); 19 | services.AddPostenClient(); 20 | services.Configure(s => s.Url = uri.ToString()); 21 | await using var serviceProvider = services.BuildServiceProvider( 22 | new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true } 23 | ); 24 | 25 | var client = serviceProvider.GetRequiredService(); 26 | 27 | var postalCodes = await client.GetPostalCodes(); 28 | 29 | await Verify(postalCodes); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/test/Altinn.Codelists.Tests/Altinn.Codelists.Tests.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/test/Altinn.Codelists.Tests/Altinn.Codelists.Tests.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/test/Altinn.Codelists.Tests/Altinn.Codelists.Tests.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/EnsureNoCodelistIdCollisions.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.Extensions; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Altinn.Codelists.Tests; 6 | 7 | public class EnsureNoCodelistIdCollisionsTest 8 | { 9 | [Fact] 10 | public void EnsureNoCodelistIdCollision() 11 | { 12 | IServiceCollection services = new ServiceCollection(); 13 | services.AddAltinnCodelists(); 14 | var serviceProvider = services.BuildServiceProvider(); 15 | var appOptionsProviders = serviceProvider.GetServices().ToList(); 16 | 17 | Assert.Empty(ValidateIdsAreUnique(appOptionsProviders)); 18 | } 19 | 20 | public static string ValidateIdsAreUnique(List objects) 21 | where T : IAppOptionsProvider 22 | { 23 | HashSet seenIds = new(); 24 | 25 | foreach (T obj in objects) 26 | { 27 | if (seenIds.Contains(obj.Id)) 28 | { 29 | return $"{obj.Id} in {obj.GetType().FullName}"; 30 | } 31 | seenIds.Add(obj.Id); 32 | } 33 | 34 | return string.Empty; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/Models/Classification.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Codelists.SSB.Models; 2 | 3 | /// 4 | /// Supported classifications as defined by SSB, ref. https://data.ssb.no/api/klass/v1/classifications 5 | /// 6 | public enum Classification 7 | { 8 | /// 9 | /// Standard for kjønn 10 | /// 11 | Sex = 2, 12 | 13 | /// 14 | /// Standard for næringsgruppering 15 | /// 16 | IndustryGrouping = 6, 17 | 18 | /// 19 | /// Standard for yrkesklassifisering 20 | /// 21 | Occupations = 7, 22 | 23 | /// 24 | /// Standard for sivilstand 25 | /// 26 | MaritalStatus = 19, 27 | 28 | /// 29 | /// Standard for grunnbeløpet i folketrygden (beløp pr. 1.5 ) 30 | /// 31 | BaseAmountNationalInsurance = 20, 32 | 33 | /// 34 | /// Standard for fylker 35 | /// 36 | Counties = 104, 37 | 38 | /// 39 | /// Standard for kommuner 40 | /// 41 | Municipalities = 131, 42 | 43 | /// 44 | /// Standard for landkoder (alfa-3) 45 | /// 46 | Countries = 552, 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Pack and publish nugets 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | build-pack: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Install dotnet 17 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 18 | with: 19 | dotnet-version: | 20 | 8.0.x 21 | - name: Install deps 22 | run: | 23 | dotnet restore 24 | - name: Build 25 | run: | 26 | dotnet build --configuration Release --no-restore -p:Deterministic=true -p:BuildNumber=${{ github.run_number }} 27 | - name: Pack 28 | run: | 29 | dotnet pack Altinn.Codelists.sln --configuration Release --no-restore --no-build -p:BuildNumber=${{ github.run_number }} -p:Deterministic=true 30 | - name: Versions 31 | run: | 32 | dotnet --version 33 | - name: Publish 34 | run: | 35 | dotnet nuget push src/**/bin/Release/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} 36 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | NU1901;NU1902;NU1903;NU1904 5 | 6 | 7 | 8 | latest 9 | true 10 | Recommended 11 | strict 12 | true 13 | all 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Posten/PostalCodesCodelistsProvider.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.App.Core.Models; 3 | using Altinn.Codelists.Posten.Clients; 4 | 5 | namespace Altinn.Codelists.Posten; 6 | 7 | /// 8 | /// Post codes and corresponding names for Norway. 9 | /// 10 | internal sealed class PostalCodesCodelistsProvider : IAppOptionsProvider 11 | { 12 | private readonly IPostalCodesClient _postalCodesClient; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | public PostalCodesCodelistsProvider(IPostalCodesClient postalCodesClient) 18 | { 19 | _postalCodesClient = postalCodesClient; 20 | } 21 | 22 | /// 23 | public string Id => "poststed"; 24 | 25 | /// 26 | public async Task GetAppOptionsAsync(string? language, Dictionary keyValuePairs) 27 | { 28 | List postalCodes = await _postalCodesClient.GetPostalCodes(); 29 | 30 | var appOptions = new AppOptions 31 | { 32 | Options = postalCodes.Select(x => new AppOption() { Value = x.PostCode, Label = x.PostalName }).ToList(), 33 | }; 34 | 35 | return appOptions; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Posten/Clients/PostalCodesCsvParser.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Codelists.Posten.Clients; 2 | 3 | /// 4 | /// Class for parsing the offical postal codes file in Norway. 5 | /// Record description: https://www.bring.no/tjenester/adressetjenester/postnummer/postnummertabeller-veiledning 6 | /// File used: https://www.bring.no/postnummerregister-ansi.txt 7 | /// Examample record: 6863 LEIKANGER 4640 SOGNDAL G 8 | /// 9 | internal sealed class PostalCodesCsvParser(Stream _csvStream) 10 | { 11 | /// 12 | /// Parses the stream provided in the constructor. 13 | /// 14 | public async Task> Parse() 15 | { 16 | List postalCodes = new(); 17 | 18 | using StreamReader reader = new StreamReader(_csvStream, Encoding.Latin1, leaveOpen: true); 19 | 20 | while (!reader.EndOfStream) 21 | { 22 | string? line = await reader.ReadLineAsync(); 23 | 24 | if (line != null) 25 | { 26 | string[] columns = line.Split('\t'); 27 | PostalCodeRecord postalCode = new(columns[0], columns[1], columns[2], columns[3], columns[4]); 28 | postalCodes.Add(postalCode); 29 | } 30 | } 31 | 32 | return postalCodes; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Posten/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.Posten.Clients; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.DependencyInjection.Extensions; 5 | 6 | namespace Altinn.Codelists.Posten; 7 | 8 | /// 9 | /// Extends the . 10 | /// 11 | public static class ServiceCollectionExtensions 12 | { 13 | /// 14 | /// Registers the services required to get postal codes through and 15 | /// 16 | public static IServiceCollection AddPosten(this IServiceCollection services) 17 | { 18 | services.AddPostenClient(); 19 | services.AddTransient(); 20 | 21 | return services; 22 | } 23 | 24 | /// 25 | /// Registers 26 | /// 27 | /// 28 | public static IServiceCollection AddPostenClient(this IServiceCollection services) 29 | { 30 | services.AddHttpClient(); 31 | services.AddOptions(); 32 | services.TryAddTransient(); 33 | 34 | return services; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/UnitsCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.Tests.SSB.Mocks; 5 | 6 | namespace Altinn.Codelists.Tests.SSB; 7 | 8 | public class UnitsCodelistProviderTests 9 | { 10 | [Fact] 11 | public async Task GetAppOptionsAsync_SortedBySelectCodes_ShouldReturnListOfCodes() 12 | { 13 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 14 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 15 | "målenheter", 16 | 303, 17 | httpClientMock, 18 | new Dictionary() 19 | { 20 | { "selectCodes", "13.03,13.04,13.02,15.06,03.01,09.03,02.02" }, 21 | { "orderBy", "selectCodes" }, 22 | }, 23 | new ClassificationOptions { MapDescriptionFunc = (classificationCode) => classificationCode.ShortName } 24 | ); 25 | 26 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 27 | 28 | Assert.NotNull(appOptions.Options); 29 | Assert.Equal(7, appOptions.Options.Count); 30 | Assert.Equal("kilogram", appOptions.Options[0].Label); 31 | Assert.Equal("stykk", appOptions.Options[^1].Label); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/SmallGameVariantTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.Tests.SSB.Mocks; 5 | 6 | namespace Altinn.Codelists.Tests.SSB; 7 | 8 | public class SmallGameVariantTests 9 | { 10 | [Fact] 11 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes() 12 | { 13 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 14 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 15 | "småvilt", 16 | 74, 17 | httpClientMock, 18 | new Dictionary 19 | { 20 | { 21 | "variant", 22 | "Hønsefugler, spurvefugler, skarver og due 2023-03 - variant av Klassifisering av småvilt 2017-04" 23 | }, 24 | }, 25 | new ClassificationOptions { MapDescriptionFunc = (classificationCode) => classificationCode.Name } 26 | ); 27 | 28 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 29 | 30 | Assert.NotNull(appOptions.Options); 31 | Assert.Equal(11, appOptions.Options.Count); 32 | Assert.Equal("Ravn", appOptions.Options.First(x => x.Value == "06").Label); 33 | Assert.Equal("Ravn", appOptions.Options.First(x => x.Value == "06").Description); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Clients/ClassificationsHttpClientTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.SSB.Clients; 2 | 3 | namespace Altinn.Codelists.Tests.SSB.Clients; 4 | 5 | public class ClassificationsHttpClientTests 6 | { 7 | //[Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")] 8 | public async Task GetClassificationCodes_ShouldReturnAllClassificationCodes() 9 | { 10 | var options = Options.Create(new ClassificationSettings()); 11 | var client = new ClassificationsHttpClient(options, new HttpClient()); 12 | 13 | var classificationCodes = await client.GetClassificationCodes(19, "nn", DateOnly.FromDateTime(DateTime.Today)); 14 | 15 | Assert.True(classificationCodes.Codes.Count > 2); 16 | } 17 | 18 | //[Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")] 19 | public async Task GetClassificationVariant_ShouldReturnAllClassificationCodeVariant() 20 | { 21 | var options = Options.Create(new ClassificationSettings()); 22 | var client = new ClassificationsHttpClient(options, new HttpClient()); 23 | 24 | var classificationCodes = await client.GetClassificationCodes( 25 | 74, 26 | "nn", 27 | new DateOnly(2023, 03, 01), 28 | "", 29 | "Hønsefugler, spurvefugler og due 2023-03 - variant av Klassifisering av småvilt 2017-04" 30 | ); 31 | 32 | Assert.True(classificationCodes.Codes.Count > 2); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Kartverket/AdministrativeUnits/MunicipalitiesCodelistProvider.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.App.Core.Models; 3 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models; 4 | 5 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits; 6 | 7 | /// 8 | /// Provides a codelist for municipalities of Norway. 9 | /// 10 | internal sealed class MunicipalitiesCodelistProvider(IAdministrativeUnitsClient _administrativeUnitsHttpClient) 11 | : IAppOptionsProvider 12 | { 13 | /// 14 | public string Id => "kommuner-kv"; 15 | 16 | /// 17 | public async Task GetAppOptionsAsync(string? language, Dictionary keyValuePairs) 18 | { 19 | bool hasCountyParam = keyValuePairs.TryGetValue("fnr", out string? countyNumber); 20 | 21 | List municipalities = 22 | hasCountyParam && countyNumber != null 23 | ? await _administrativeUnitsHttpClient.GetMunicipalities(countyNumber) 24 | : await _administrativeUnitsHttpClient.GetMunicipalities(); 25 | 26 | var appOptions = new AppOptions() 27 | { 28 | Options = municipalities.Select(x => new AppOption() { Value = x.Number, Label = x.Name }).ToList(), 29 | Parameters = 30 | hasCountyParam && countyNumber != null 31 | ? new Dictionary() { { "fnr", countyNumber } } 32 | : new Dictionary(), 33 | }; 34 | 35 | return appOptions; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Altinn.Codelists.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Altinn;Studio;App;Core 6 | 7 | This class library holds extra codelists for Altinn Studio apps. 8 | 9 | 10 | direct 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | <_Parameter1>PackageReference.%(PackageReference.Identity) 28 | <_Parameter2 Condition="'%(PackageReference.VersionOverride)' != ''">%(PackageReference.VersionOverride) 29 | <_Parameter2 Condition="'%(PackageReference.VersionOverride)' == ''">%(PackageReference.Version) 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Utilities/PredicateBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace Altinn.Codelists.Utilities; 4 | 5 | /// 6 | /// Simple predicate builder 7 | /// 8 | internal static class PredicateBuilder 9 | { 10 | /// 11 | /// Creates a predicate that evaluates to true 12 | /// 13 | public static Expression> True() 14 | { 15 | return f => true; 16 | } 17 | 18 | /// 19 | /// Creates a predicate that evaluates to false 20 | /// 21 | public static Expression> False() 22 | { 23 | return f => false; 24 | } 25 | 26 | /// 27 | /// Combines the first predicate with the second using the logical "or". 28 | /// 29 | public static Expression> Or(this Expression> expr1, Expression> expr2) 30 | { 31 | var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast()); 32 | return Expression.Lambda>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters); 33 | } 34 | 35 | /// 36 | /// Combines the first predicate with the second using the logical "and". 37 | /// 38 | public static Expression> And( 39 | this Expression> expr1, 40 | Expression> expr2 41 | ) 42 | { 43 | var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast()); 44 | return Expression.Lambda>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Posten/Clients/PostalCodeRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Codelists.Posten.Clients; 2 | 3 | /// 4 | /// Information about a postal code ie. a record representing one line in the postal codes csv file. 5 | /// 6 | public record PostalCodeRecord 7 | { 8 | /// 9 | /// Creates an instance of 10 | /// 11 | public PostalCodeRecord( 12 | string postCode, 13 | string postalName, 14 | string municipalityNumber, 15 | string municipalityName, 16 | string category 17 | ) 18 | { 19 | PostCode = postCode; 20 | PostalName = postalName; 21 | MunicipalityNumber = municipalityNumber; 22 | MunicipalityName = municipalityName; 23 | Category = category; 24 | } 25 | 26 | /// 27 | /// Post code 28 | /// 29 | public string PostCode { get; init; } 30 | 31 | /// 32 | /// Postal name 33 | /// 34 | public string PostalName { get; init; } 35 | 36 | /// 37 | /// Municipality number 38 | /// 39 | public string MunicipalityNumber { get; init; } 40 | 41 | /// 42 | /// Municipality name 43 | /// 44 | public string MunicipalityName { get; init; } 45 | 46 | /// 47 | /// Category. 48 | /// B = Both streeet addresses and P.O.Boxes 49 | /// F = Multiple areas of uses(Common) 50 | /// G = Street addresses 51 | /// P = P.O.Boxes 52 | /// S = Postcode for special service(not used for addresses) 53 | /// 54 | public string Category { get; init; } 55 | } 56 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/SexCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.SSB.Models; 5 | using Altinn.Codelists.Tests.SSB.Mocks; 6 | 7 | namespace Altinn.Codelists.Tests.SSB; 8 | 9 | public class SexCodelistProviderTests 10 | { 11 | [Fact] 12 | public async Task GetAppOptionsAsync_EnumProvided_ShouldReturnListOfCodes() 13 | { 14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 16 | "sex", 17 | Classification.Sex, 18 | httpClientMock 19 | ); 20 | 21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 22 | 23 | Assert.NotNull(appOptions.Options); 24 | Assert.Equal(2, appOptions.Options.Count); 25 | Assert.Equal("Kvinne", appOptions.Options.First(x => x.Value == "2").Label); 26 | } 27 | 28 | [Fact] 29 | public async Task GetAppOptionsAsync_IdProvided_ShouldReturnListOfCodes() 30 | { 31 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 32 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider("sex", 2, httpClientMock); 33 | 34 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 35 | 36 | Assert.NotNull(appOptions.Options); 37 | Assert.Equal(2, appOptions.Options.Count); 38 | Assert.Equal("Mann", appOptions.Options.First(x => x.Value == "1").Label); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Posten/PostalCodesCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.Posten; 3 | using Altinn.Codelists.Tests.Posten.Clients; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Altinn.Codelists.Tests.Posten; 7 | 8 | public class PostalCodesCodelistProviderTests 9 | { 10 | [Fact] 11 | public async Task Get() 12 | { 13 | await using var fixture = await PostenFixture.Create(proxy: false); 14 | var server = fixture.Server; 15 | var baseUrl = server.Url; 16 | Assert.NotNull(baseUrl); 17 | var baseUri = new Uri(baseUrl); 18 | var uri = new Uri(baseUri, PostenSettings.DefaultPath); 19 | 20 | var services = new ServiceCollection(); 21 | services.AddPosten(); 22 | services.Configure(s => s.Url = uri.ToString()); 23 | await using var serviceProvider = services.BuildServiceProvider( 24 | new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true } 25 | ); 26 | 27 | var client = serviceProvider.GetRequiredService(); 28 | var optionsProvider = serviceProvider.GetRequiredService(); 29 | 30 | var postalCodes = await client.GetPostalCodes(); 31 | var options = await optionsProvider.GetAppOptionsAsync(null, []); 32 | 33 | var clientCodes = postalCodes.Select(p => p.PostCode).ToHashSet(); 34 | Assert.NotNull(options.Options); 35 | var optionsCodes = options 36 | .Options.Select(o => 37 | { 38 | Assert.NotNull(o.Value); 39 | return o.Value; 40 | }) 41 | .ToHashSet(); 42 | 43 | Assert.True(clientCodes.SetEquals(optionsCodes)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Utilities/EmbeddedResource.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Altinn.Codelists.Utilities; 4 | 5 | /// 6 | /// Helper class for embeded resources. 7 | /// 8 | internal static class EmbeddedResource 9 | { 10 | /// 11 | /// Finds an embeded resource, by name, within the executing assembly and reads it as string. 12 | /// 13 | /// 14 | /// 15 | public async static Task LoadDataAsString(string resourceName) 16 | { 17 | var resourceStream = LoadDataAsStream(resourceName); 18 | 19 | using var reader = new StreamReader(resourceStream); 20 | string text = await reader.ReadToEndAsync(); 21 | 22 | return text; 23 | } 24 | 25 | /// 26 | /// Finds an embeded resource, by name, within the executing assembly and reads it as a 27 | /// 28 | /// The name of the resource including namespace. 29 | /// 30 | public static Stream LoadDataAsStream(string resourceName) 31 | { 32 | var assembly = Assembly.GetExecutingAssembly(); 33 | Stream? resourceStream = assembly.GetManifestResourceStream(resourceName); 34 | try 35 | { 36 | if (resourceStream == null) 37 | { 38 | throw new InvalidOperationException( 39 | $"Unable to find resource {resourceName} embedded in assembly {assembly.FullName}." 40 | ); 41 | } 42 | 43 | resourceStream.Seek(0, SeekOrigin.Begin); 44 | 45 | return resourceStream; 46 | } 47 | catch (Exception) 48 | { 49 | resourceStream?.Dispose(); 50 | throw; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Clients/AdministrativeUnitsClientTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.Kartverket.AdministrativeUnits; 2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Clients; 3 | 4 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Clients; 5 | 6 | public class AdministrativeUnitsClientTests 7 | { 8 | [Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")] 9 | public async Task GetCounties_NothingSpecified_ShouldReturnAllCounties() 10 | { 11 | var client = new AdministrativeUnitsHttpClient( 12 | Options.Create(new AdministrativeUnitsSettings()), 13 | new HttpClient() 14 | ); 15 | 16 | var counties = await client.GetCounties(); 17 | 18 | Assert.True(counties.Count > 2); 19 | } 20 | 21 | [Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")] 22 | public async Task GetMunicipalities_NothingSpecified_ShouldReturnAllMunicipalities() 23 | { 24 | var client = new AdministrativeUnitsHttpClient( 25 | Options.Create(new AdministrativeUnitsSettings()), 26 | new HttpClient() 27 | ); 28 | 29 | var municipalities = await client.GetMunicipalities(); 30 | 31 | Assert.True(municipalities.Count > 2); 32 | } 33 | 34 | [Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")] 35 | public async Task GetMunicipalities_CountySpecified_ShouldReturnMunicipalitiesWithinCounty() 36 | { 37 | var client = new AdministrativeUnitsHttpClient( 38 | Options.Create(new AdministrativeUnitsSettings()), 39 | new HttpClient() 40 | ); 41 | 42 | var municipalities = await client.GetMunicipalities("46"); 43 | 44 | Assert.True(municipalities.Count > 2); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Kartverket/AdministrativeUnits/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Clients; 3 | using Microsoft.Extensions.Caching.Memory; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.DependencyInjection.Extensions; 6 | 7 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Extensions; 8 | 9 | /// 10 | /// Extends the . 11 | /// 12 | public static class ServiceCollectionExtensions 13 | { 14 | /// 15 | /// Registers the services required to get support for Kartverkets counties (fylker) and municipalities (kommuner) codelists. 16 | /// 17 | public static IServiceCollection AddKartverketAdministrativeUnits(this IServiceCollection services) 18 | { 19 | services.AddKartverketAdministrativeUnitsClient(); 20 | services.AddTransient(); 21 | services.AddTransient(); 22 | 23 | return services; 24 | } 25 | 26 | /// 27 | /// Registers the client services required to get support for Kartverkets counties (fylker) and municipalities (kommuner) codelists. 28 | /// 29 | /// 30 | /// 31 | public static IServiceCollection AddKartverketAdministrativeUnitsClient(this IServiceCollection services) 32 | { 33 | services.AddMemoryCache(); 34 | services.AddOptions(); 35 | services.AddHttpClient(); 36 | services.TryAddTransient(sp => new AdministrativeUnitsHttpClientCached( 37 | ActivatorUtilities.CreateInstance(sp), 38 | sp.GetRequiredService() 39 | )); 40 | 41 | return services; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Altinn.Codelists.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32228.430 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.Codelists.Tests", "test\Altinn.Codelists.Tests\Altinn.Codelists.Tests.csproj", "{659F246C-3286-45EA-BD4C-84822EE6C841}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.Codelists", "src\Altinn.Codelists\Altinn.Codelists.csproj", "{9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6E1FAB62-D893-46C4-8510-306E62E9CDCD}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {659F246C-3286-45EA-BD4C-84822EE6C841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {659F246C-3286-45EA-BD4C-84822EE6C841}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {659F246C-3286-45EA-BD4C-84822EE6C841}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {659F246C-3286-45EA-BD4C-84822EE6C841}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {1736ED5E-5025-4F87-9881-48A863054B17} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @(ReleaseNoteLines, '%0a') 17 | 18 | 19 | 20 | 21 | Recommended 22 | enable 23 | enable 24 | true 25 | $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)).Parent.FullName) 26 | preview.0 27 | v 28 | true 29 | 30 | 31 | 32 | 33 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BuildNumber) 34 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BuildNumber) 35 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BuildNumber) 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | true 45 | 46 | 47 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/MunicipalitiesCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.Kartverket.AdministrativeUnits; 3 | using Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Mocks; 4 | 5 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits; 6 | 7 | public class MunicipalitiesCodelistProviderTests 8 | { 9 | [Fact] 10 | public async Task GetAppOptionsAsync_NoCountySpecified_ShouldReturnListOfAllMunicipalities() 11 | { 12 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock( 13 | Options.Create(new AdministrativeUnitsSettings()) 14 | ); 15 | IAppOptionsProvider appOptionsProvider = new MunicipalitiesCodelistProvider(administrativeUnitsHttpClientMock); 16 | 17 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 18 | 19 | Assert.NotNull(appOptions.Options); 20 | Assert.Equal(356, appOptions.Options.Count); 21 | Assert.Equal("Sogndal", appOptions.Options.First(x => x.Value == "4640").Label); 22 | Assert.Equal("Brønnøy", appOptions.Options.First(x => x.Value == "1813").Label); 23 | } 24 | 25 | [Fact] 26 | public async Task GetAppOptionsAsync_CountySpecified_ShouldReturnListOfMunicipalitiesByCounty() 27 | { 28 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock( 29 | Options.Create(new AdministrativeUnitsSettings()) 30 | ); 31 | IAppOptionsProvider appOptionsProvider = new MunicipalitiesCodelistProvider(administrativeUnitsHttpClientMock); 32 | 33 | var appOptions = await appOptionsProvider.GetAppOptionsAsync( 34 | "nb", 35 | new Dictionary() { { "fnr", "46" } } 36 | ); 37 | 38 | Assert.NotNull(appOptions.Options); 39 | Assert.Equal(43, appOptions.Options.Count); 40 | Assert.Equal("Sogndal", appOptions.Options.First(x => x.Value == "4640").Label); 41 | Assert.Null(appOptions.Options.FirstOrDefault(x => x.Value == "1813")); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Testdata/units.json: -------------------------------------------------------------------------------- 1 | { 2 | "codes": [ 3 | { 4 | "code": "02.02", 5 | "parentCode": "02", 6 | "level": "2", 7 | "name": "stykk", 8 | "shortName": "stk", 9 | "presentationName": "", 10 | "validFrom": null, 11 | "validTo": null, 12 | "notes": "Passer for statistikk der en teller ting, f.eks. så og så mange stykk av en viss varetype" 13 | }, 14 | { 15 | "code": "03.01", 16 | "parentCode": "03", 17 | "level": "2", 18 | "name": "kvadratmeter", 19 | "shortName": "m²", 20 | "presentationName": "", 21 | "validFrom": null, 22 | "validTo": null, 23 | "notes": "En kvadratmeter er et areal på en ganger en meter." 24 | }, 25 | { 26 | "code": "09.03", 27 | "parentCode": "09", 28 | "level": "2", 29 | "name": "meter", 30 | "shortName": "m", 31 | "presentationName": "", 32 | "validFrom": null, 33 | "validTo": null, 34 | "notes": "En meter er grunnenheten for lengde i SI-systemet." 35 | }, 36 | { 37 | "code": "13.02", 38 | "parentCode": "13", 39 | "level": "2", 40 | "name": "gram", 41 | "shortName": "g", 42 | "presentationName": "", 43 | "validFrom": null, 44 | "validTo": null, 45 | "notes": "1 g= 1/1 000 kg." 46 | }, 47 | { 48 | "code": "13.03", 49 | "parentCode": "13", 50 | "level": "2", 51 | "name": "kilogram", 52 | "shortName": "kg", 53 | "presentationName": "", 54 | "validFrom": null, 55 | "validTo": null, 56 | "notes": "Kilogram er grunnenheten for masse i SI-systemet." 57 | }, 58 | { 59 | "code": "13.04", 60 | "parentCode": "13", 61 | "level": "2", 62 | "name": "tonn", 63 | "shortName": "t", 64 | "presentationName": "", 65 | "validFrom": null, 66 | "validTo": null, 67 | "notes": "1 t=1 000 kg." 68 | }, 69 | { 70 | "code": "15.06", 71 | "parentCode": "15", 72 | "level": "2", 73 | "name": "kubikkmeter", 74 | "shortName": "m³", 75 | "presentationName": "", 76 | "validFrom": null, 77 | "validTo": null, 78 | "notes": "En kubikkmeter tilsvarer volumet i en kube der alle sidene er en meter." 79 | } 80 | ] 81 | } -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Testdata/classifications.http: -------------------------------------------------------------------------------- 1 | ### 2 | # Oversikt: https://www.ssb.no/klass/ 3 | GET https://data.ssb.no/api/klass/v1/classifications?size=150&language=en 4 | 5 | ### 6 | # Søk etter klassifikasjon 7 | GET https://data.ssb.no/api/klass/v1/classifications/search?query=land 8 | 9 | ### 10 | # Land (ISO alfa 3) 11 | GET https://data.ssb.no/api/klass/v1/classifications/552/codesAt?date=2023-03-03 12 | Accept: application/json;charset=utf-8 13 | 14 | ### 15 | # Fylker 16 | GET https://data.ssb.no/api/klass/v1/classifications/104/codes?from=2022-07-10 17 | Accept: application/json;charset=utf-8 18 | 19 | ### 20 | # Kommuner 21 | GET https://data.ssb.no/api/klass/v1/classifications/131/codesAt?date=2023-03-02 22 | Accept: application/json;charset=utf-8 23 | 24 | ### 25 | # Kjønn 26 | GET https://data.ssb.no/api/klass/v1/classifications/2/codes?from=2022-07-10&selectLevel=1 27 | Accept: application/json;charset=utf-8 28 | 29 | ### 30 | # Næringsgruppering 31 | GET https://data.ssb.no/api/klass/v1/classifications/6/codesAt?date=2023-02-11&language=en&selectLevel=1 32 | Accept: application/json;charset=utf-8 33 | 34 | ### 35 | GET https://data.ssb.no/api/klass/v1/classifications/6/codesAt?date=2023-02-11&language=nb 36 | Accept: application/json;charset=utf-8 37 | 38 | ### 39 | # Yrkesklassifisering 40 | GET https://data.ssb.no/api/klass/v1/classifications/7/codesAt?date=2023-02-11&language=nb 41 | Accept: application/json;charset=utf-8 42 | 43 | ### 44 | # Sivilstand 45 | GET https://data.ssb.no/api/klass/v1/classifications/19/codesAt?date=2023-02-11&language=nn 46 | Accept: application/json;charset=utf-8 47 | 48 | ### 49 | # Grunnbeløp i folketrygden 50 | GET https://data.ssb.no/api/klass/v1/classifications/20/codesAt?date=2023-03-12 51 | Accept: application/json;charset=utf-8 52 | 53 | ### 54 | # Småvilt med variant 55 | GET https://data.ssb.no/api/klass/v1/classifications/74/codesAt?date=2023-03-12&language=nn 56 | Accept: application/json;charset=utf-8 57 | 58 | ### 59 | GET https://data.ssb.no/api/klass/v1/classifications/74/variantAt?language=nn&date=2023-03-03&variantName=Hønsefugler, spurvefugler og due 2023-03 - variant av Klassifisering av småvilt 2017-04 60 | Accept: application/json;charset=utf-8 61 | 62 | ### 63 | 64 | GET https://data.ssb.no/api/klass/v1/variants/2103?language=nb 65 | Accept: application/json;charset=utf-8 66 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/RestCountries/Clients/CountriesClient.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.RestCountries.Data; 2 | using Altinn.Codelists.RestCountries.Models; 3 | using Altinn.Codelists.Utilities; 4 | 5 | namespace Altinn.Codelists.RestCountries.Clients; 6 | 7 | /// 8 | /// Client to get information on all countries of the world. 9 | /// Note that this is not an http client but uses a static json embedded within 10 | /// this dll to resolve the the list of countries. 11 | /// 12 | internal sealed class CountriesClient : ICountryClient 13 | { 14 | /// 15 | /// Sends a asynchronus internal request to get all the countries of the world. 16 | /// 17 | public async Task> GetCountries() 18 | { 19 | var filters = new List(); 20 | 21 | return await GetCountries(filters); 22 | } 23 | 24 | /// 25 | /// Sends a asynchronus internal request to get all countries of the world, 26 | /// matching the specified filters. 27 | /// Values within the same filter object are AND'ed, 28 | /// while values between filter objects are OR'ed. 29 | /// 30 | public async Task> GetCountries(IEnumerable filters) 31 | { 32 | string json = await EmbeddedResource.LoadDataAsString(Resources.CountriesJson); 33 | var countries = JsonSerializer.Deserialize>(json) ?? new List(); 34 | 35 | IQueryable query = BuildQuery(countries, filters); 36 | 37 | return query.ToList(); 38 | } 39 | 40 | private static IQueryable BuildQuery(IEnumerable countries, IEnumerable filters) 41 | { 42 | var query = countries.AsQueryable(); 43 | 44 | var predicate = filters.Any() ? PredicateBuilder.False() : PredicateBuilder.True(); 45 | 46 | foreach (var filter in filters) 47 | { 48 | var subPredicate = PredicateBuilder.True(); 49 | if (!string.IsNullOrEmpty(filter.Region)) 50 | { 51 | subPredicate = subPredicate.And(c => c.Region.Equals(filter.Region, StringComparison.Ordinal)); 52 | } 53 | 54 | if (!string.IsNullOrEmpty(filter.SubRegion)) 55 | { 56 | subPredicate = subPredicate.And(c => c.SubRegion.Equals(filter.SubRegion, StringComparison.Ordinal)); 57 | } 58 | 59 | predicate = predicate.Or(subPredicate); 60 | } 61 | 62 | return query.Where(predicate); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ "main" ] 9 | schedule: 10 | - cron: '37 20 * * 3' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'csharp' ] 25 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 26 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 31 | 32 | # Initializes the CodeQL tools for scanning. 33 | - name: Initialize CodeQL 34 | uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4 35 | with: 36 | languages: ${{ matrix.language }} 37 | queries: security-extended,security-and-quality 38 | # If you wish to specify custom queries, you can do so here or in a config file. 39 | # By default, queries listed here will override any specified in a config file. 40 | # Prefix the list here with "+" to use these queries and those in the config file. 41 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 42 | 43 | 44 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 45 | # If this step fails, then you should remove it and run the build manually (see below) 46 | - name: Autobuild 47 | uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4 48 | 49 | # ℹ️ Command-line programs to run using the OS shell. 50 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 51 | 52 | # If the Autobuild fails above, remove it and uncomment the following three lines. 53 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 54 | 55 | # - run: | 56 | # echo "Run, Build Application using script" 57 | # ./location_of_script_within_repo/buildscript.sh 58 | 59 | - name: Perform CodeQL Analysis 60 | uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4 61 | with: 62 | category: "/language:${{matrix.language}}" 63 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/RestCountries/Clients/CountriesClientTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.RestCountries; 2 | using Altinn.Codelists.RestCountries.Models; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Altinn.Codelists.Tests.RestCountries.Clients; 6 | 7 | public class CountriesClientTests 8 | { 9 | private readonly ITestOutputHelper _output; 10 | 11 | public CountriesClientTests(ITestOutputHelper outputHelper) 12 | { 13 | _output = outputHelper; 14 | } 15 | 16 | private sealed record Fixture(IServiceProvider ServiceProvider, ICountryClient Client) : IAsyncDisposable 17 | { 18 | public async ValueTask DisposeAsync() 19 | { 20 | if (ServiceProvider is IAsyncDisposable disposable) 21 | await disposable.DisposeAsync(); 22 | } 23 | 24 | public static Fixture Create() 25 | { 26 | var services = new ServiceCollection(); 27 | services.AddRestCountriesClient(); 28 | 29 | var serviceProvider = services.BuildServiceProvider( 30 | new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true } 31 | ); 32 | 33 | var client = serviceProvider.GetRequiredService(); 34 | return new(serviceProvider, client); 35 | } 36 | } 37 | 38 | [Fact] 39 | public async Task GetCountries_NoFilter_ShouldReturnAll() 40 | { 41 | await using var fixture = Fixture.Create(); 42 | var client = fixture.Client; 43 | 44 | var countries = await client.GetCountries(); 45 | 46 | Assert.Equal(250, countries.Count); 47 | } 48 | 49 | [Fact] 50 | public async Task GetCountries_FilterOnRegion_ShouldReturnOnlyInRegion() 51 | { 52 | await using var fixture = Fixture.Create(); 53 | var client = fixture.Client; 54 | 55 | var countries = await client.GetCountries(new List() { new Filter() { Region = "Europe" } }); 56 | 57 | Assert.Equal(53, countries.Count); 58 | } 59 | 60 | [Fact] 61 | public async Task GetCountries_FilterOnMultipleRegions_ShouldReturnOnlyInRegions() 62 | { 63 | await using var fixture = Fixture.Create(); 64 | var client = fixture.Client; 65 | 66 | var countries = await client.GetCountries( 67 | new List() 68 | { 69 | new Filter() { Region = "Europe" }, 70 | new Filter() { Region = "Asia", SubRegion = "Eastern Asia" }, 71 | } 72 | ); 73 | 74 | countries.ForEach(c => 75 | { 76 | _output.WriteLine(c.Name.Common); 77 | }); 78 | Assert.Equal(61, countries.Count); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /.github/workflows/test-and-analyze-fork.yml: -------------------------------------------------------------------------------- 1 | name: Code test and analysis (fork) 2 | on: 3 | pull_request: 4 | branches: [ main ] 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | jobs: 7 | test: 8 | if: github.repository_owner == 'Altinn' && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true) 9 | name: Build and Test 10 | runs-on: windows-latest 11 | steps: 12 | - name: Setup .NET 13 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 14 | with: 15 | dotnet-version: | 16 | 8.0.x 17 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 18 | with: 19 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 20 | 21 | - name: dotnet build 22 | run: dotnet build Altinn.Codelists.sln -v m 23 | 24 | - name: dotnet test 25 | run: dotnet test Altinn.Codelists.sln --results-directory TestResults/ --collect:"XPlat Code Coverage" -v m 26 | 27 | - name: Generate coverage results 28 | run: | 29 | dotnet tool install --global dotnet-reportgenerator-globaltool 30 | reportgenerator -reports:TestResults/**/coverage.cobertura.xml -targetdir:TestResults/Output/CoverageReport -reporttypes:Cobertura 31 | 32 | - name: Archive code coverage results 33 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 34 | with: 35 | name: code-coverage-report 36 | path: TestResults/Output/CoverageReport/ 37 | 38 | code-coverage: 39 | if: github.repository_owner == 'Altinn' && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) 40 | name: Report code coverage 41 | runs-on: ubuntu-latest 42 | needs: test 43 | steps: 44 | - name: Download Coverage Results 45 | uses: actions/download-artifact@master 46 | with: 47 | name: code-coverage-report 48 | path: dist/ 49 | - name: Create Coverage Summary Report 50 | uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0 51 | with: 52 | filename: dist/Cobertura.xml 53 | badge: true 54 | fail_below_min: true 55 | format: markdown 56 | hide_branch_rate: false 57 | hide_complexity: true 58 | indicators: true 59 | output: both 60 | thresholds: '60 80' 61 | 62 | - name: Add Coverage PR Comment 63 | uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 64 | with: 65 | recreate: true 66 | path: code-coverage-results.md 67 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/ClassificationOptions.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.SSB.Clients; 2 | 3 | namespace Altinn.Codelists.SSB; 4 | 5 | /// 6 | /// Controls various options when returning data from the SSB Classification API. 7 | /// 8 | public class ClassificationOptions 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public ClassificationOptions() { } 14 | 15 | /// 16 | /// Maps the classification notes field to the description field. 17 | /// 18 | public bool MapNotesToDescription { get; set; } = false; 19 | 20 | /// 21 | /// Defines a custom mapping function for the description field. 22 | /// 23 | public Func? MapDescriptionFunc { get; set; } 24 | 25 | /// 26 | /// Returns the description for the provided . 27 | /// If is set, this will be used. 28 | /// If is set, the notes field will be used. 29 | /// If is not set and no 30 | /// is specified, an empty string will be returned. 31 | /// 32 | public string GetDescription(ClassificationCode classificationCode) 33 | { 34 | if (MapDescriptionFunc == null) 35 | { 36 | return MapNotesToDescription ? classificationCode.Notes : string.Empty; 37 | } 38 | 39 | return MapDescriptionFunc.Invoke(classificationCode); 40 | } 41 | 42 | /// 43 | /// Maps the classification notes field to the help text field. 44 | /// 45 | public bool MapNotesToHelpText { get; set; } = true; 46 | 47 | /// 48 | /// Defines a custom mapping function for the helptext field. 49 | /// 50 | public Func? MapHelpTextFunc { get; set; } 51 | 52 | /// 53 | /// Returns the helptext for the provided . 54 | /// If is set, this will be used. 55 | /// If is set, the notes field will be used. 56 | /// If is not set and no 57 | /// is specified, an empty string will be returned. 58 | /// 59 | public string GetHelpText(ClassificationCode classificationCode) 60 | { 61 | if (MapHelpTextFunc == null) 62 | { 63 | return MapNotesToHelpText ? classificationCode.Notes : string.Empty; 64 | } 65 | 66 | return MapHelpTextFunc.Invoke(classificationCode); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/Kartverket/AdministrativeUnits/Clients/AdministrativeUnitsHttpClient.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models; 2 | using Microsoft.Extensions.Options; 3 | 4 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Clients; 5 | 6 | /// 7 | /// Http client to get information on norways offical administrative units for counties and municipalities . 8 | /// 9 | internal sealed class AdministrativeUnitsHttpClient : IAdministrativeUnitsClient 10 | { 11 | private readonly HttpClient _httpClient; 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | public AdministrativeUnitsHttpClient(IOptions settings, HttpClient httpClient) 17 | { 18 | _httpClient = httpClient; 19 | _httpClient.BaseAddress = new Uri(settings.Value.BaseApiUrl); 20 | _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); 21 | } 22 | 23 | /// 24 | /// Sends a asynchronus GET request to get all the counties of Norway. 25 | /// 26 | public async Task> GetCounties() 27 | { 28 | using var response = await _httpClient.GetAsync("fylker"); 29 | var responseJson = await response.Content.ReadAsStringAsync(); 30 | 31 | var counties = JsonSerializer.Deserialize>(responseJson); 32 | 33 | return counties ?? new List(); 34 | } 35 | 36 | /// 37 | /// Sends a asynchronus GET request to get all the unicipalities of Norway. 38 | /// 39 | public async Task> GetMunicipalities() 40 | { 41 | using var response = await _httpClient.GetAsync("kommuner"); 42 | var responseJson = await response.Content.ReadAsStringAsync(); 43 | 44 | var municipalities = JsonSerializer.Deserialize>(responseJson); 45 | 46 | return municipalities ?? new List(); 47 | } 48 | 49 | /// 50 | /// Sends a asynchronus GET request to get all the municipalities within the specified county. 51 | /// 52 | /// County number (string) including leading zero's indentifying the county. 53 | public async Task> GetMunicipalities(string countyNumber) 54 | { 55 | using var response = await _httpClient.GetAsync( 56 | $"fylker/{countyNumber}?filtrer=kommuner,fylkesnavn,fylkesnummer" 57 | ); 58 | var responseJson = await response.Content.ReadAsStringAsync(); 59 | 60 | var county = JsonSerializer.Deserialize(responseJson); 61 | 62 | return county?.Municipalities ?? new List(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/ClassificationOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.SSB; 2 | using Altinn.Codelists.SSB.Clients; 3 | 4 | namespace Altinn.Codelists.Tests.SSB; 5 | 6 | public class ClassificationOptionsTests 7 | { 8 | [Fact] 9 | public void ClassificationOptions_True_MapNotesToDescription() 10 | { 11 | var classificationOptions = new ClassificationOptions() { MapNotesToDescription = true }; 12 | 13 | Assert.Equal( 14 | "Test", 15 | classificationOptions.GetDescription(new ClassificationCode("1", "Ja", "1") { Notes = "Test" }) 16 | ); 17 | } 18 | 19 | [Fact] 20 | public void ClassificationOptions_False_MapNotesToDescription() 21 | { 22 | var classificationOptions = new ClassificationOptions(); 23 | 24 | Assert.Empty(classificationOptions.GetDescription(new ClassificationCode("1", "Ja", "1"))); 25 | } 26 | 27 | [Theory] 28 | [InlineData(true)] 29 | [InlineData(false)] 30 | public void ClassificationOptions_FunctionProvided_FunctionShouldMapToDescription(bool mapNotesToDescription) 31 | { 32 | var classificationOptions = new ClassificationOptions() 33 | { 34 | MapDescriptionFunc = (classificationCode) => classificationCode.Notes, 35 | MapNotesToDescription = mapNotesToDescription, 36 | }; 37 | var classificationCode = new ClassificationCode("9112", "Renholdere i virksomheter", "4") { Notes = "Test" }; 38 | 39 | Assert.Equal("Test", classificationOptions.GetDescription(classificationCode)); 40 | } 41 | 42 | [Fact] 43 | public void ClassificationOptions_True_MapNotesToHelpText() 44 | { 45 | var classificationOptions = new ClassificationOptions() { MapNotesToDescription = true }; 46 | 47 | Assert.Equal( 48 | "Test", 49 | classificationOptions.GetHelpText(new ClassificationCode("1", "Ja", "1") { Notes = "Test" }) 50 | ); 51 | } 52 | 53 | [Fact] 54 | public void ClassificationOptions_False_MapNotesToHelpText() 55 | { 56 | var classificationOptions = new ClassificationOptions(); 57 | 58 | Assert.Empty(classificationOptions.GetHelpText(new ClassificationCode("1", "Ja", "1"))); 59 | } 60 | 61 | [Theory] 62 | [InlineData(true)] 63 | [InlineData(false)] 64 | public void ClassificationOptions_FunctionProvided_FunctionShouldMapToHelpText(bool mapNotesToHelpText) 65 | { 66 | var classificationOptions = new ClassificationOptions() 67 | { 68 | MapDescriptionFunc = (classificationCode) => classificationCode.Notes, 69 | MapNotesToDescription = mapNotesToHelpText, 70 | }; 71 | var classificationCode = new ClassificationCode("9112", "Renholdere i virksomheter", "4") { Notes = "Test" }; 72 | 73 | Assert.Equal("Test", classificationOptions.GetHelpText(classificationCode)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Posten/Clients/PostenFixture.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Altinn.Codelists.Posten; 3 | using WireMock.Server; 4 | using WireMock.Settings; 5 | 6 | namespace Altinn.Codelists.Tests.Posten.Clients; 7 | 8 | internal sealed class PostenFixture : IAsyncDisposable 9 | { 10 | public WireMockServer Server { get; } 11 | 12 | private PostenFixture(WireMockServer server) => Server = server; 13 | 14 | public ValueTask DisposeAsync() 15 | { 16 | Server.Dispose(); 17 | return default; 18 | } 19 | 20 | /// 21 | /// Starts a mock server for bring API 22 | /// - Proxy == true: it will just proxy the request through to the actual bring API, and save the request and response as a mapping 23 | /// - Proxy == false: serve the static mapping in the mappings folder 24 | /// 25 | /// To update the test data/mapping, just pass proxy == true 26 | /// In addition we can trim down the result set: 27 | /// 1. `wget https://www.bring.no/postnummerregister-ansi.txt` 28 | /// 2. `sed -i '10,5060 d' postnummerregister-ansi.txt` 29 | /// 3. `cat postnummerregister-ansi.txt | base64 -w 0` 30 | /// 4. Update the `Response.BodyAsBytes` property of the mapping JSON file 31 | /// 5. Update content length response header based on `stat postnummerregister-ansi.txt` 32 | /// 6. Remove header matching for hostname and such in the mapping JSON file 33 | /// Next time the test is ran with proxy == false, it will use the updated mapping 34 | /// 35 | public static Task Create(bool proxy) 36 | { 37 | var server = StartServer(proxy); 38 | return Task.FromResult(new PostenFixture(server)); 39 | } 40 | 41 | private static WireMockServer StartServer(bool proxy, [CallerFilePath] string filePath = "") 42 | { 43 | WireMockServerSettings settings; 44 | if (proxy) 45 | { 46 | settings = new WireMockServerSettings 47 | { 48 | StartAdminInterface = true, 49 | ProxyAndRecordSettings = new ProxyAndRecordSettings 50 | { 51 | Url = PostenSettings.DefaultBaseUrl, 52 | SaveMapping = true, 53 | SaveMappingToFile = true, 54 | SaveMappingForStatusCodePattern = "2xx", 55 | PrefixForSavedMappingFile = "wiremock", 56 | }, 57 | }; 58 | } 59 | else 60 | { 61 | settings = new WireMockServerSettings { }; 62 | } 63 | var server = WireMockServer.Start(settings); 64 | var dir = new FileInfo(filePath).Directory?.FullName; 65 | Assert.NotNull(dir); 66 | dir = Path.Join(dir, "mappings"); 67 | if (proxy) 68 | { 69 | server.SaveStaticMappings(dir); 70 | } 71 | else 72 | { 73 | server.ReadStaticMappings(dir); 74 | } 75 | 76 | return server; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.github/workflows/test-and-analyze.yml: -------------------------------------------------------------------------------- 1 | name: Code test and analysis 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | types: [opened, synchronize, reopened] 8 | workflow_dispatch: 9 | jobs: 10 | analyze: 11 | if: | 12 | github.repository_owner == 'Altinn' && 13 | (github.event_name != 'pull_request' && github.event.repository.fork == false) || 14 | (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) 15 | name: Static code analysis 16 | runs-on: windows-latest 17 | steps: 18 | - name: Setup .NET 19 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 20 | with: 21 | dotnet-version: | 22 | 8.0.x 23 | - name: Set up JDK 11 24 | uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5 25 | with: 26 | distribution: 'zulu' 27 | java-version: 17 28 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 29 | with: 30 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 31 | - name: Cache SonarCloud packages 32 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 33 | with: 34 | path: ~\sonar\cache 35 | key: ${{ runner.os }}-sonar 36 | restore-keys: ${{ runner.os }}-sonar 37 | - name: Cache SonarCloud scanner 38 | id: cache-sonar-scanner 39 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 40 | with: 41 | path: .\.sonar\scanner 42 | key: ${{ runner.os }}-sonar-scanner 43 | restore-keys: ${{ runner.os }}-sonar-scanner 44 | - name: Install SonarCloud scanner 45 | if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' 46 | shell: powershell 47 | run: | 48 | New-Item -Path .\.sonar\scanner -ItemType Directory 49 | dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner 50 | - name: Build & Test 51 | run: | 52 | dotnet test Altinn.Codelists.sln -v m 53 | - name: Analyze 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 56 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 57 | shell: powershell 58 | run: | 59 | .\.sonar\scanner\dotnet-sonarscanner begin /k:"Altinn_codelists-lib-dotnet" /o:"altinn" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vstest.reportsPaths="**/*.trx" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" 60 | 61 | dotnet build Altinn.Codelists.sln 62 | dotnet test Altinn.Codelists.sln ` 63 | --no-build ` 64 | --results-directory TestResults/ ` 65 | --collect:"XPlat Code Coverage" ` 66 | -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover 67 | 68 | .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" 69 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/Clients/ClassificationCode.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Codelists.SSB.Clients; 4 | 5 | /// 6 | /// Represents a single classification code. 7 | /// 8 | public class ClassificationCode(string code, string name, string level) 9 | { 10 | /// 11 | /// Unique classification code 12 | /// 13 | [JsonPropertyName("code")] 14 | public string Code { get; set; } = code; 15 | 16 | /// 17 | /// If part of a hierarchy, this is a reference to the parent 18 | /// 19 | [JsonPropertyName("parentCode")] 20 | public string? ParentCode { get; set; } = null; 21 | 22 | /// 23 | /// Hierarchy level for classifications thats hierarchical. Non hierarchical classifications 24 | /// will have a value of one. 25 | /// 26 | [JsonPropertyName("level")] 27 | public string Level { get; set; } = level; 28 | 29 | /// 30 | /// Classification name. 31 | /// 32 | [JsonPropertyName("name")] 33 | public string Name { get; set; } = name; 34 | 35 | /// 36 | /// Classification short name. 37 | /// 38 | [JsonPropertyName("shortName")] 39 | public string ShortName { get; set; } = string.Empty; 40 | 41 | /// 42 | /// Classification presentation name. 43 | /// 44 | [JsonPropertyName("presentationName")] 45 | public string PresentationName { get; set; } = string.Empty; 46 | 47 | /// 48 | /// When the the classification is valid from. 49 | /// 50 | [JsonPropertyName("validFrom")] 51 | public DateTime? ValidFrom { get; set; } 52 | 53 | /// 54 | /// When the classification is valid to. 55 | /// 56 | [JsonPropertyName("validTo")] 57 | public DateTime? ValidTo { get; set; } 58 | 59 | /// 60 | /// Given a range (from/to) that spans dates where the classification has changed, 61 | /// this will show the from date the classification is valid. In such a response 62 | /// you will get multiple entries and will have to use 63 | /// and to separate them. 64 | /// 65 | [JsonPropertyName("validFromInRequestedRange")] 66 | public DateTime? ValidFromInRequestedRange { get; set; } 67 | 68 | /// 69 | /// Given a range (from/to) that spans dates where the classification has changed, 70 | /// this will show the to date the classification is valid. In such a response 71 | /// you will get multiple entries and will have to use 72 | /// and to separate them. 73 | /// 74 | [JsonPropertyName("validToInRequestedRange")] 75 | public DateTime? ValidToInRequestedRange { get; set; } 76 | 77 | /// 78 | /// Textual notes added to the classification code. 79 | /// 80 | [JsonPropertyName("notes")] 81 | public string Notes { get; set; } = string.Empty; 82 | } 83 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Testdata/smallGame_Variant.json: -------------------------------------------------------------------------------- 1 | { 2 | "codes": [ 3 | { 4 | "code": "01", 5 | "parentCode": null, 6 | "level": "1", 7 | "name": "Storfugl", 8 | "shortName": "", 9 | "presentationName": "", 10 | "validFrom": null, 11 | "validTo": null, 12 | "notes": "" 13 | }, 14 | { 15 | "code": "02", 16 | "parentCode": null, 17 | "level": "1", 18 | "name": "Orrfugl", 19 | "shortName": "", 20 | "presentationName": "", 21 | "validFrom": null, 22 | "validTo": null, 23 | "notes": "" 24 | }, 25 | { 26 | "code": "04", 27 | "parentCode": null, 28 | "level": "1", 29 | "name": "Jerpe", 30 | "shortName": "", 31 | "presentationName": "", 32 | "validFrom": null, 33 | "validTo": null, 34 | "notes": "" 35 | }, 36 | { 37 | "code": "05", 38 | "parentCode": null, 39 | "level": "1", 40 | "name": "Ringdue", 41 | "shortName": "", 42 | "presentationName": "", 43 | "validFrom": null, 44 | "validTo": null, 45 | "notes": "" 46 | }, 47 | { 48 | "code": "06", 49 | "parentCode": null, 50 | "level": "1", 51 | "name": "Ravn", 52 | "shortName": "", 53 | "presentationName": "", 54 | "validFrom": null, 55 | "validTo": null, 56 | "notes": "" 57 | }, 58 | { 59 | "code": "07", 60 | "parentCode": null, 61 | "level": "1", 62 | "name": "Kråke", 63 | "shortName": "", 64 | "presentationName": "", 65 | "validFrom": null, 66 | "validTo": null, 67 | "notes": "" 68 | }, 69 | { 70 | "code": "08.1", 71 | "parentCode": null, 72 | "level": "1", 73 | "name": "Skjære", 74 | "shortName": "", 75 | "presentationName": "", 76 | "validFrom": null, 77 | "validTo": null, 78 | "notes": "" 79 | }, 80 | { 81 | "code": "08.2", 82 | "parentCode": null, 83 | "level": "1", 84 | "name": "Nøtteskrike", 85 | "shortName": "", 86 | "presentationName": "", 87 | "validFrom": null, 88 | "validTo": null, 89 | "notes": "" 90 | }, 91 | { 92 | "code": "09.1", 93 | "parentCode": null, 94 | "level": "1", 95 | "name": "Gråtrost", 96 | "shortName": "", 97 | "presentationName": "", 98 | "validFrom": null, 99 | "validTo": null, 100 | "notes": "" 101 | }, 102 | { 103 | "code": "15.1", 104 | "parentCode": null, 105 | "level": "1", 106 | "name": "Toppskarv", 107 | "shortName": "", 108 | "presentationName": "", 109 | "validFrom": null, 110 | "validTo": null, 111 | "notes": "" 112 | }, 113 | { 114 | "code": "15.2", 115 | "parentCode": null, 116 | "level": "1", 117 | "name": "Storskarv", 118 | "shortName": "", 119 | "presentationName": "", 120 | "validFrom": null, 121 | "validTo": null, 122 | "notes": "" 123 | } 124 | ] 125 | } -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Mocks/AdministrativeUnitsHttpClientMock.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.Kartverket.AdministrativeUnits; 2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Clients; 3 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models; 4 | using RichardSzalay.MockHttp; 5 | 6 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Mocks; 7 | 8 | public class AdministrativeUnitsHttpClientMock : IAdministrativeUnitsClient 9 | { 10 | private const string COUNTIES_TESTDATA_RESOURCE = 11 | "Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Testdata.fylker.json"; 12 | private const string COUNTY_MUNICIPALITIES_TESTDATA_RESOURCE = 13 | "Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Testdata.fylke46_kommuner.json"; 14 | private const string MUNICIPALITIES_TESTDATA_RESOURCE = 15 | "Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Testdata.kommuner.json"; 16 | 17 | private readonly IAdministrativeUnitsClient _administrativeUnitsHttpClient; 18 | private readonly IOptions _administrativeUnitsOptions; 19 | 20 | public MockHttpMessageHandler HttpMessageHandlerMock { get; private set; } 21 | public MockedRequest MockedCountiesRequest { get; private set; } 22 | public MockedRequest MockedMunicipalitiesRequest { get; private set; } 23 | 24 | public AdministrativeUnitsHttpClientMock(IOptions administrativeUnitsOptions) 25 | { 26 | _administrativeUnitsOptions = administrativeUnitsOptions; 27 | 28 | HttpMessageHandlerMock = new MockHttpMessageHandler(); 29 | MockedCountiesRequest = HttpMessageHandlerMock 30 | .When("https://ws.geonorge.no/kommuneinfo/v1/fylker") 31 | .Respond("application/json", EmbeddedResource.LoadDataAsString(COUNTIES_TESTDATA_RESOURCE).Result); 32 | 33 | MockedMunicipalitiesRequest = HttpMessageHandlerMock 34 | .When("https://ws.geonorge.no/kommuneinfo/v1/kommuner") 35 | .Respond("application/json", EmbeddedResource.LoadDataAsString(MUNICIPALITIES_TESTDATA_RESOURCE).Result); 36 | 37 | MockedMunicipalitiesRequest = HttpMessageHandlerMock 38 | .When("https://ws.geonorge.no/kommuneinfo/v1/fylker/46?filtrer=kommuner,fylkesnavn,fylkesnummer") 39 | .Respond( 40 | "application/json", 41 | EmbeddedResource.LoadDataAsString(COUNTY_MUNICIPALITIES_TESTDATA_RESOURCE).Result 42 | ); 43 | 44 | _administrativeUnitsHttpClient = new AdministrativeUnitsHttpClient( 45 | _administrativeUnitsOptions, 46 | new HttpClient(HttpMessageHandlerMock) 47 | ); 48 | } 49 | 50 | public Task> GetCounties() 51 | { 52 | return _administrativeUnitsHttpClient.GetCounties(); 53 | } 54 | 55 | public Task> GetMunicipalities() 56 | { 57 | return _administrativeUnitsHttpClient.GetMunicipalities(); 58 | } 59 | 60 | public Task> GetMunicipalities(string countyNumber) 61 | { 62 | return _administrativeUnitsHttpClient.GetMunicipalities(countyNumber); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/OccupationsCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.SSB.Models; 5 | using Altinn.Codelists.Tests.SSB.Mocks; 6 | 7 | namespace Altinn.Codelists.Tests.SSB; 8 | 9 | public class OccupationsCodelistProviderTests 10 | { 11 | [Fact] 12 | public async Task GetAppOptionsAsync_AllLevels_ShouldReturnListOfCodes() 13 | { 14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 16 | "yrker", 17 | Classification.Occupations, 18 | httpClientMock 19 | ); 20 | 21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 22 | 23 | Assert.NotNull(appOptions.Options); 24 | Assert.Equal(582, appOptions.Options.Count); 25 | Assert.Equal("Akademiske yrker", appOptions.Options.First(x => x.Value == "2").Label); 26 | Assert.Equal("IKT-rådgivere", appOptions.Options.First(x => x.Value == "25").Label); 27 | Assert.Equal( 28 | "Programvare- og applikasjonsutviklere/analytikere", 29 | appOptions.Options.First(x => x.Value == "251").Label 30 | ); 31 | Assert.Equal("Programvareutviklere", appOptions.Options.First(x => x.Value == "2512").Label); 32 | } 33 | 34 | [Fact] 35 | public async Task GetAppOptionsAsync_FirstLevelOnly_ShouldReturnListOfCodes() 36 | { 37 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 38 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 39 | "yrker", 40 | Classification.Occupations, 41 | httpClientMock 42 | ); 43 | 44 | var appOptions = await appOptionsProvider.GetAppOptionsAsync( 45 | "nb", 46 | new Dictionary() { { "level", "1" } } 47 | ); 48 | 49 | Assert.NotNull(appOptions.Options); 50 | Assert.Equal(10, appOptions.Options.Count); 51 | Assert.Equal("Militære yrker og uoppgitt", appOptions.Options.First(x => x.Value == "0").Label); 52 | } 53 | 54 | [Fact] 55 | public async Task GetAppOptionsAsync_DefaultFirstLevel_ShouldReturnListOfCodes() 56 | { 57 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 58 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 59 | "næringsgruppering", 60 | Classification.IndustryGrouping, 61 | httpClientMock, 62 | new Dictionary() { { "level", "1" } } 63 | ); 64 | 65 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 66 | 67 | Assert.NotNull(appOptions.Options); 68 | Assert.Equal(21, appOptions.Options.Count); 69 | Assert.Equal("Jordbruk, skogbruk og fiske", appOptions.Options.First(x => x.Value == "A").Label); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/IndustryGroupingCodelistProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB; 3 | using Altinn.Codelists.SSB.Clients; 4 | using Altinn.Codelists.SSB.Models; 5 | using Altinn.Codelists.Tests.SSB.Mocks; 6 | 7 | namespace Altinn.Codelists.Tests.SSB; 8 | 9 | public class IndustryGroupingCodelistProviderTests 10 | { 11 | [Fact] 12 | public async Task GetAppOptionsAsync_AllLevels_ShouldReturnListOfCodes() 13 | { 14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 16 | "næringsgruppering", 17 | Classification.IndustryGrouping, 18 | httpClientMock 19 | ); 20 | 21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 22 | 23 | Assert.NotNull(appOptions.Options); 24 | Assert.Equal(1811, appOptions.Options.Count); 25 | Assert.Equal("Jordbruk, skogbruk og fiske", appOptions.Options.First(x => x.Value == "A").Label); 26 | Assert.Equal( 27 | "Jordbruk og tjenester tilknyttet jordbruk, jakt og viltstell", 28 | appOptions.Options.First(x => x.Value == "01").Label 29 | ); 30 | Assert.Equal("Dyrking av ettårige vekster", appOptions.Options.First(x => x.Value == "01.1").Label); 31 | } 32 | 33 | [Fact] 34 | public async Task GetAppOptionsAsync_FirstLevelOnly_ShouldReturnListOfCodes() 35 | { 36 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 37 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 38 | "næringsgruppering", 39 | Classification.IndustryGrouping, 40 | httpClientMock 41 | ); 42 | 43 | var appOptions = await appOptionsProvider.GetAppOptionsAsync( 44 | "nb", 45 | new Dictionary() { { "level", "1" } } 46 | ); 47 | 48 | Assert.NotNull(appOptions.Options); 49 | Assert.Equal(21, appOptions.Options.Count); 50 | Assert.Equal("Jordbruk, skogbruk og fiske", appOptions.Options.First(x => x.Value == "A").Label); 51 | Assert.Equal("1", appOptions.Parameters.First(x => x.Key == "level").Value); 52 | } 53 | 54 | [Fact] 55 | public async Task GetAppOptionsAsync_DefaultFirstLevel_ShouldReturnListOfCodes() 56 | { 57 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings())); 58 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider( 59 | "næringsgruppering", 60 | Classification.IndustryGrouping, 61 | httpClientMock, 62 | new Dictionary() { { "level", "1" } } 63 | ); 64 | 65 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary()); 66 | 67 | Assert.NotNull(appOptions.Options); 68 | Assert.Equal(21, appOptions.Options.Count); 69 | Assert.Equal("Jordbruk, skogbruk og fiske", appOptions.Options.First(x => x.Value == "A").Label); 70 | Assert.Equal("nb", appOptions.Parameters.First(x => x.Key == "language").Value); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Altinn.Codelists.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | runtime; build; native; contentfiles; analyzers; buildtransitive 51 | all 52 | 53 | 54 | runtime; build; native; contentfiles; analyzers; buildtransitive 55 | all 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Testdata/maritalStatus.json: -------------------------------------------------------------------------------- 1 | { 2 | "codes": [ 3 | { 4 | "code": "1", 5 | "parentCode": null, 6 | "level": "1", 7 | "name": "Ugift", 8 | "shortName": "", 9 | "presentationName": "", 10 | "validFrom": null, 11 | "validTo": null, 12 | "validFromInRequestedRange": "2022-07-10", 13 | "validToInRequestedRange": null, 14 | "notes": "" 15 | }, 16 | { 17 | "code": "2", 18 | "parentCode": null, 19 | "level": "1", 20 | "name": "Gift", 21 | "shortName": "", 22 | "presentationName": "", 23 | "validFrom": null, 24 | "validTo": null, 25 | "validFromInRequestedRange": "2022-07-10", 26 | "validToInRequestedRange": null, 27 | "notes": "" 28 | }, 29 | { 30 | "code": "3", 31 | "parentCode": null, 32 | "level": "1", 33 | "name": "Enke/enkemann", 34 | "shortName": "", 35 | "presentationName": "", 36 | "validFrom": null, 37 | "validTo": null, 38 | "validFromInRequestedRange": "2022-07-10", 39 | "validToInRequestedRange": null, 40 | "notes": "" 41 | }, 42 | { 43 | "code": "4", 44 | "parentCode": null, 45 | "level": "1", 46 | "name": "Skilt", 47 | "shortName": "", 48 | "presentationName": "", 49 | "validFrom": null, 50 | "validTo": null, 51 | "validFromInRequestedRange": "2022-07-10", 52 | "validToInRequestedRange": null, 53 | "notes": "" 54 | }, 55 | { 56 | "code": "5", 57 | "parentCode": null, 58 | "level": "1", 59 | "name": "Separert", 60 | "shortName": "", 61 | "presentationName": "", 62 | "validFrom": null, 63 | "validTo": null, 64 | "validFromInRequestedRange": "2022-07-10", 65 | "validToInRequestedRange": null, 66 | "notes": "" 67 | }, 68 | { 69 | "code": "6", 70 | "parentCode": null, 71 | "level": "1", 72 | "name": "Registrert partner", 73 | "shortName": "", 74 | "presentationName": "", 75 | "validFrom": null, 76 | "validTo": null, 77 | "validFromInRequestedRange": "2022-07-10", 78 | "validToInRequestedRange": null, 79 | "notes": "" 80 | }, 81 | { 82 | "code": "7", 83 | "parentCode": null, 84 | "level": "1", 85 | "name": "Separert partner", 86 | "shortName": "", 87 | "presentationName": "", 88 | "validFrom": null, 89 | "validTo": null, 90 | "validFromInRequestedRange": "2022-07-10", 91 | "validToInRequestedRange": null, 92 | "notes": "" 93 | }, 94 | { 95 | "code": "8", 96 | "parentCode": null, 97 | "level": "1", 98 | "name": "Skilt partner", 99 | "shortName": "", 100 | "presentationName": "", 101 | "validFrom": null, 102 | "validTo": null, 103 | "validFromInRequestedRange": "2022-07-10", 104 | "validToInRequestedRange": null, 105 | "notes": "" 106 | }, 107 | { 108 | "code": "9", 109 | "parentCode": null, 110 | "level": "1", 111 | "name": "Gjenlevende partner", 112 | "shortName": "", 113 | "presentationName": "", 114 | "validFrom": null, 115 | "validTo": null, 116 | "validFromInRequestedRange": "2022-07-10", 117 | "validToInRequestedRange": null, 118 | "notes": "" 119 | } 120 | ] 121 | } -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Clients/ClassificationsHttpClientCachedTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.SSB.Clients; 2 | using Altinn.Codelists.Tests.SSB.Mocks; 3 | using Microsoft.Extensions.Caching.Memory; 4 | 5 | namespace Altinn.Codelists.Tests.SSB.Clients; 6 | 7 | public class ClassificationsHttpClientCachedTests 8 | { 9 | [Fact] 10 | public async Task GetCodes_EmptyCache_ShouldReturnValues() 11 | { 12 | var classificationsHttpClientMock = new ClassificationsHttpClientMock( 13 | Options.Create(new ClassificationSettings()) 14 | ); 15 | var classificationsHttpClientCached = new ClassificationsHttpClientCached( 16 | classificationsHttpClientMock, 17 | new MemoryCache(new MemoryCacheOptions()) 18 | ); 19 | 20 | var maritalStatus = await classificationsHttpClientCached.GetClassificationCodes(19); 21 | 22 | Assert.Equal(9, maritalStatus.Codes.Count); 23 | } 24 | 25 | [Fact] 26 | public async Task GetCounties_CacheFilled_ShouldReturnFromCache() 27 | { 28 | var classificationsHttpClientMock = new ClassificationsHttpClientMock( 29 | Options.Create(new ClassificationSettings()) 30 | ); 31 | var classificationsHttpClientCached = new ClassificationsHttpClientCached( 32 | classificationsHttpClientMock, 33 | new MemoryCache(new MemoryCacheOptions()) 34 | ); 35 | 36 | // First request will fill the cache 37 | _ = await classificationsHttpClientCached.GetClassificationCodes(19); 38 | 39 | // Second request should not trigger another http request from the client 40 | var maritalStatus = await classificationsHttpClientCached.GetClassificationCodes(19); 41 | 42 | Assert.Equal(9, maritalStatus.Codes.Count); 43 | Assert.Equal( 44 | 1, 45 | classificationsHttpClientMock.HttpMessageHandlerMock.GetMatchCount( 46 | classificationsHttpClientMock.MockedMaritalStatusRequest 47 | ) 48 | ); 49 | } 50 | 51 | [Fact] 52 | public async Task GetCounties_CacheExpired_ShouldPopulateAgain() 53 | { 54 | var classificationsHttpClientMock = new ClassificationsHttpClientMock( 55 | Options.Create(new ClassificationSettings()) 56 | ); 57 | var classificationsHttpClientCached = new ClassificationsHttpClientCached( 58 | classificationsHttpClientMock, 59 | new MemoryCache(new MemoryCacheOptions()), 60 | () => 61 | { 62 | // Let the cache entry live for 100 milliseconds 63 | return new MemoryCacheEntryOptions() 64 | { 65 | AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100), 66 | Priority = CacheItemPriority.Normal, 67 | }; 68 | } 69 | ); 70 | 71 | // First request will fill the cache 72 | await classificationsHttpClientCached.GetClassificationCodes(19); 73 | 74 | // Wait for the cached entry to be evicted 75 | await Task.Delay(200); 76 | 77 | // This should trigger another http request and fill the cache again 78 | var maritalStatusCodes = await classificationsHttpClientCached.GetClassificationCodes(19); 79 | 80 | Assert.Equal(9, maritalStatusCodes.Codes.Count); 81 | Assert.Equal( 82 | 2, 83 | classificationsHttpClientMock.HttpMessageHandlerMock.GetMatchCount( 84 | classificationsHttpClientMock.MockedMaritalStatusRequest 85 | ) 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Clients/CountiesClientCachedTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.Kartverket.AdministrativeUnits; 2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Clients; 3 | using Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Mocks; 4 | using Microsoft.Extensions.Caching.Memory; 5 | 6 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Clients; 7 | 8 | public class CountiesClientCachedTests 9 | { 10 | [Fact] 11 | public async Task GetCounties_EmptyCache_ShouldReturnAllCounties() 12 | { 13 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock( 14 | Options.Create(new AdministrativeUnitsSettings()) 15 | ); 16 | var administrativeUnitsHttpClientCached = new AdministrativeUnitsHttpClientCached( 17 | administrativeUnitsHttpClientMock, 18 | new MemoryCache(new MemoryCacheOptions()) 19 | ); 20 | 21 | var counties = await administrativeUnitsHttpClientCached.GetCounties(); 22 | 23 | Assert.Equal(11, counties.Count); 24 | } 25 | 26 | [Fact] 27 | public async Task GetCounties_CacheFilled_ShouldReturnFromCache() 28 | { 29 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock( 30 | Options.Create(new AdministrativeUnitsSettings()) 31 | ); 32 | var administrativeUnitsHttpClientCached = new AdministrativeUnitsHttpClientCached( 33 | administrativeUnitsHttpClientMock, 34 | new MemoryCache(new MemoryCacheOptions()) 35 | ); 36 | 37 | // First request will fill the cache 38 | await administrativeUnitsHttpClientCached.GetCounties(); 39 | 40 | // Second request should not trigger another http request from the client 41 | var counties = await administrativeUnitsHttpClientCached.GetCounties(); 42 | 43 | Assert.Equal(11, counties.Count); 44 | Assert.Equal( 45 | 1, 46 | administrativeUnitsHttpClientMock.HttpMessageHandlerMock.GetMatchCount( 47 | administrativeUnitsHttpClientMock.MockedCountiesRequest 48 | ) 49 | ); 50 | } 51 | 52 | [Fact] 53 | public async Task GetCounties_CacheExpired_ShouldPopulateAgain() 54 | { 55 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock( 56 | Options.Create(new AdministrativeUnitsSettings()) 57 | ); 58 | var administrativeUnitsHttpClientCached = new AdministrativeUnitsHttpClientCached( 59 | administrativeUnitsHttpClientMock, 60 | new MemoryCache(new MemoryCacheOptions()), 61 | () => 62 | { 63 | // Let the cache entry live for 100 milliseconds 64 | return new MemoryCacheEntryOptions() 65 | { 66 | AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100), 67 | Priority = CacheItemPriority.Normal, 68 | }; 69 | } 70 | ); 71 | 72 | // First request will fill the cache 73 | await administrativeUnitsHttpClientCached.GetCounties(); 74 | 75 | // Wait for the cached entry to be evicted 76 | await Task.Delay(200); 77 | 78 | // This should trigger another http request and fill the cache again 79 | var counties = await administrativeUnitsHttpClientCached.GetCounties(); 80 | 81 | Assert.Equal(11, counties.Count); 82 | Assert.Equal( 83 | 2, 84 | administrativeUnitsHttpClientMock.HttpMessageHandlerMock.GetMatchCount( 85 | administrativeUnitsHttpClientMock.MockedCountiesRequest 86 | ) 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/RestCountries/Models/Country.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Codelists.RestCountries.Models; 4 | 5 | /// 6 | /// Holds information of a country. 7 | /// 8 | public class Country(Name name) 9 | { 10 | /// 11 | /// The name of the country (in english) 12 | /// 13 | [JsonPropertyName("name")] 14 | public Name Name { get; set; } = name; 15 | 16 | /// 17 | /// ISO 3166-1 alpha-2 – two-letter country codes which are the most widely used of the three, 18 | /// and used most prominently for the Internet's country code top-level domains (with a few exceptions). 19 | /// 20 | [JsonPropertyName("cca2")] 21 | public string CountryCodeAlpha2 { get; set; } = ""; 22 | 23 | /// 24 | /// ISO 3166-1 alpha-3 – three-letter country codes which allow a better visual association 25 | /// between the codes and the country names than the alpha-2 codes. 26 | /// 27 | [JsonPropertyName("ccn3")] 28 | public string CountryCodeNumeric3 { get; set; } = ""; 29 | 30 | /// 31 | /// ISO 3166-1 numeric – three-digit country codes which are identical to those developed and maintained 32 | /// by the United Nations Statistics Division, with the advantage of script (writing system) independence, 33 | /// and hence useful for people or systems using non-Latin scripts. 34 | /// 35 | [JsonPropertyName("cca3")] 36 | public string CountryCodeAlpha3 { get; set; } = ""; 37 | 38 | /// 39 | /// ISO 3166-1 independence status (denotes the country is considered a sovereign state) 40 | /// 41 | [JsonPropertyName("independent")] 42 | public bool Independent { get; set; } 43 | 44 | /// 45 | /// ISO 3166-1 assignment status 46 | /// 47 | [JsonPropertyName("status")] 48 | public string Status { get; set; } = ""; 49 | 50 | /// 51 | /// UN Member status 52 | /// 53 | [JsonPropertyName("unMember")] 54 | public bool UnitedNationsMember { get; set; } 55 | 56 | /// 57 | /// Emojii flag character 58 | /// 59 | [JsonPropertyName("flag")] 60 | public string EmojiFlag { get; set; } = ""; 61 | 62 | /// 63 | /// Region (Africa, Americas, Antarctic, Asia, Europe, Oceania) 64 | /// 65 | [JsonPropertyName("region")] 66 | public string Region { get; set; } = ""; 67 | 68 | /// 69 | /// Subregion 70 | /// 71 | [JsonPropertyName("subregion")] 72 | public string SubRegion { get; set; } = ""; 73 | 74 | /// 75 | /// Official languages 76 | /// 77 | [JsonPropertyName("languages")] 78 | public Dictionary Languages { get; set; } = new Dictionary(); 79 | 80 | /// 81 | /// Name of country translated to various languages. 82 | /// 83 | [JsonPropertyName("translations")] 84 | public Dictionary Translations { get; set; } = new Dictionary(); 85 | 86 | /// 87 | /// Latitude and longitude 88 | /// 89 | [JsonPropertyName("latlng")] 90 | public decimal[] LatitudeLongitude { get; set; } = Array.Empty(); 91 | 92 | /// 93 | /// Top level domains 94 | /// 95 | [JsonPropertyName("tld")] 96 | public string[] TopLevelDomains { get; set; } = Array.Empty(); 97 | 98 | /// 99 | /// Currencies used 100 | /// 101 | [JsonPropertyName("currencies")] 102 | public Dictionary Currencies { get; set; } = new Dictionary(); 103 | } 104 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/Clients/ClassificationsHttpClientCached.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using Microsoft.Extensions.Caching.Memory; 3 | 4 | namespace Altinn.Codelists.SSB.Clients; 5 | 6 | /// 7 | /// Http client to get classification codes from SSB. 8 | /// This is a decorator class for 9 | /// that caches the information for performance reasons. 10 | /// 11 | internal sealed class ClassificationsHttpClientCached : IClassificationsClient 12 | { 13 | private readonly IClassificationsClient _classificationsClient; 14 | private readonly IMemoryCache _memoryCache; 15 | private readonly Func _getCacheEntryOptions; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | public ClassificationsHttpClientCached(IClassificationsClient classificationsClient, IMemoryCache memoryCache) 21 | : this(classificationsClient, memoryCache, DefaultCacheEntryOptions) 22 | { 23 | _classificationsClient = classificationsClient; 24 | _memoryCache = memoryCache; 25 | } 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public ClassificationsHttpClientCached( 31 | IClassificationsClient classificationsClient, 32 | IMemoryCache memoryCache, 33 | Func getCacheEntryOptionsFunc 34 | ) 35 | { 36 | _classificationsClient = classificationsClient; 37 | _memoryCache = memoryCache; 38 | _getCacheEntryOptions = getCacheEntryOptionsFunc; 39 | } 40 | 41 | /// 42 | public async Task GetClassificationCodes( 43 | int classificationId, 44 | string language = "nb", 45 | DateOnly? atDate = null, 46 | string level = "", 47 | string variant = "", 48 | string selectCodes = "" 49 | ) 50 | { 51 | var cacheKey = GetCacheKey(classificationId, language, atDate, level, variant, selectCodes); 52 | 53 | var codes = await _memoryCache.GetOrCreateAsync( 54 | cacheKey, 55 | async cacheEntry => 56 | { 57 | var cacheEntryOptions = _getCacheEntryOptions.Invoke(); 58 | cacheEntry.SetOptions(cacheEntryOptions); 59 | var data = await _classificationsClient.GetClassificationCodes( 60 | classificationId, 61 | language, 62 | atDate, 63 | level, 64 | variant, 65 | selectCodes 66 | ); 67 | 68 | if (data is null) 69 | { 70 | cacheEntry.Dispose(); 71 | return null; 72 | } 73 | 74 | return data; 75 | } 76 | ); 77 | 78 | return codes ?? new ClassificationCodes() { Codes = new List() }; 79 | } 80 | 81 | private static string GetCacheKey( 82 | int classificationId, 83 | string language, 84 | DateOnly? atDate, 85 | string level, 86 | string variant, 87 | string selectCodes 88 | ) 89 | { 90 | return $"{classificationId}_{language}_{atDate?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}_{level}_{variant}_{selectCodes}"; 91 | } 92 | 93 | // Expires the cache entry at midnight, to get potential new or removed entries. 94 | private static MemoryCacheEntryOptions DefaultCacheEntryOptions() 95 | { 96 | DateTime expirationTime = DateTime.Today.AddDays(1); 97 | 98 | return new MemoryCacheEntryOptions() 99 | { 100 | AbsoluteExpiration = expirationTime, 101 | Priority = CacheItemPriority.Normal, 102 | }; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Testdata/counties.json: -------------------------------------------------------------------------------- 1 | { 2 | "codes": [ 3 | { 4 | "code": "03", 5 | "parentCode": null, 6 | "level": "1", 7 | "name": "Oslo", 8 | "shortName": "", 9 | "presentationName": "", 10 | "validFrom": null, 11 | "validTo": null, 12 | "validFromInRequestedRange": "2022-07-10", 13 | "validToInRequestedRange": null, 14 | "notes": "" 15 | }, 16 | { 17 | "code": "11", 18 | "parentCode": null, 19 | "level": "1", 20 | "name": "Rogaland", 21 | "shortName": "", 22 | "presentationName": "", 23 | "validFrom": null, 24 | "validTo": null, 25 | "validFromInRequestedRange": "2022-07-10", 26 | "validToInRequestedRange": null, 27 | "notes": "" 28 | }, 29 | { 30 | "code": "15", 31 | "parentCode": null, 32 | "level": "1", 33 | "name": "Møre og Romsdal", 34 | "shortName": "", 35 | "presentationName": "", 36 | "validFrom": null, 37 | "validTo": null, 38 | "validFromInRequestedRange": "2022-07-10", 39 | "validToInRequestedRange": null, 40 | "notes": "" 41 | }, 42 | { 43 | "code": "18", 44 | "parentCode": null, 45 | "level": "1", 46 | "name": "Nordland - Nordlánnda", 47 | "shortName": "", 48 | "presentationName": "", 49 | "validFrom": null, 50 | "validTo": null, 51 | "validFromInRequestedRange": "2022-07-10", 52 | "validToInRequestedRange": null, 53 | "notes": "Stortingets vedtak fra 27. april 2021 innfører tospråklig navn på Nordland fylke, norsk og lulesamisk. Endringen trer i kraft straks. " 54 | }, 55 | { 56 | "code": "30", 57 | "parentCode": null, 58 | "level": "1", 59 | "name": "Viken", 60 | "shortName": "", 61 | "presentationName": "", 62 | "validFrom": null, 63 | "validTo": null, 64 | "validFromInRequestedRange": "2022-07-10", 65 | "validToInRequestedRange": null, 66 | "notes": "" 67 | }, 68 | { 69 | "code": "34", 70 | "parentCode": null, 71 | "level": "1", 72 | "name": "Innlandet", 73 | "shortName": "", 74 | "presentationName": "", 75 | "validFrom": null, 76 | "validTo": null, 77 | "validFromInRequestedRange": "2022-07-10", 78 | "validToInRequestedRange": null, 79 | "notes": "" 80 | }, 81 | { 82 | "code": "38", 83 | "parentCode": null, 84 | "level": "1", 85 | "name": "Vestfold og Telemark", 86 | "shortName": "", 87 | "presentationName": "", 88 | "validFrom": null, 89 | "validTo": null, 90 | "validFromInRequestedRange": "2022-07-10", 91 | "validToInRequestedRange": null, 92 | "notes": "" 93 | }, 94 | { 95 | "code": "42", 96 | "parentCode": null, 97 | "level": "1", 98 | "name": "Agder", 99 | "shortName": "", 100 | "presentationName": "", 101 | "validFrom": null, 102 | "validTo": null, 103 | "validFromInRequestedRange": "2022-07-10", 104 | "validToInRequestedRange": null, 105 | "notes": "" 106 | }, 107 | { 108 | "code": "46", 109 | "parentCode": null, 110 | "level": "1", 111 | "name": "Vestland", 112 | "shortName": "", 113 | "presentationName": "", 114 | "validFrom": null, 115 | "validTo": null, 116 | "validFromInRequestedRange": "2022-07-10", 117 | "validToInRequestedRange": null, 118 | "notes": "" 119 | }, 120 | { 121 | "code": "50", 122 | "parentCode": null, 123 | "level": "1", 124 | "name": "Trøndelag - Trööndelage", 125 | "shortName": "", 126 | "presentationName": "", 127 | "validFrom": null, 128 | "validTo": null, 129 | "validFromInRequestedRange": "2022-07-10", 130 | "validToInRequestedRange": null, 131 | "notes": "" 132 | }, 133 | { 134 | "code": "54", 135 | "parentCode": null, 136 | "level": "1", 137 | "name": "Troms og Finnmark - Romsa ja Finnmárku - Tromssa ja Finmarkku", 138 | "shortName": "", 139 | "presentationName": "", 140 | "validFrom": null, 141 | "validTo": null, 142 | "validFromInRequestedRange": "2022-07-10", 143 | "validToInRequestedRange": null, 144 | "notes": "" 145 | }, 146 | { 147 | "code": "99", 148 | "parentCode": null, 149 | "level": "1", 150 | "name": "Uoppgitt", 151 | "shortName": "", 152 | "presentationName": "", 153 | "validFrom": null, 154 | "validTo": null, 155 | "validFromInRequestedRange": "2022-07-10", 156 | "validToInRequestedRange": null, 157 | "notes": "" 158 | } 159 | ] 160 | } -------------------------------------------------------------------------------- /src/Altinn.Codelists/Kartverket/AdministrativeUnits/Clients/AdministrativeUnitsHttpClientCached.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models; 2 | using Microsoft.Extensions.Caching.Memory; 3 | 4 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Clients; 5 | 6 | /// 7 | /// Http client to get information on norways offical administrative units for counties and municipalities. 8 | /// This class caches the information for performance reasons to avoid costly http calls. 9 | /// 10 | internal sealed class AdministrativeUnitsHttpClientCached : IAdministrativeUnitsClient 11 | { 12 | private const string COUNTIES_CACHE_KEY = "counties"; 13 | private const string MUNICIPALITIES_CACHE_KEY_BASE = "municipalities"; 14 | 15 | private readonly IAdministrativeUnitsClient _administrativeUnitsClient; 16 | private readonly IMemoryCache _memoryCache; 17 | private readonly Func _getCacheEntryOptions; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | public AdministrativeUnitsHttpClientCached(IAdministrativeUnitsClient countiesClient, IMemoryCache memoryCache) 23 | : this(countiesClient, memoryCache, DefaultCacheEntryOptions) { } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | public AdministrativeUnitsHttpClientCached( 29 | IAdministrativeUnitsClient countiesClient, 30 | IMemoryCache memoryCache, 31 | Func getCacheEntryOptionsFunc 32 | ) 33 | { 34 | _administrativeUnitsClient = countiesClient; 35 | _memoryCache = memoryCache; 36 | _getCacheEntryOptions = getCacheEntryOptionsFunc; 37 | } 38 | 39 | /// 40 | public async Task> GetCounties() 41 | { 42 | var counties = await _memoryCache.GetOrCreateAsync( 43 | COUNTIES_CACHE_KEY, 44 | async cacheEntry => 45 | { 46 | var cacheEntryOptions = _getCacheEntryOptions.Invoke(); 47 | cacheEntry.SetOptions(cacheEntryOptions); 48 | 49 | return await _administrativeUnitsClient.GetCounties(); 50 | } 51 | ); 52 | 53 | return counties ?? new List(); 54 | } 55 | 56 | /// 57 | public async Task> GetMunicipalities() 58 | { 59 | var municipalities = await _memoryCache.GetOrCreateAsync( 60 | MUNICIPALITIES_CACHE_KEY_BASE, 61 | async cacheEntry => 62 | { 63 | var cacheEntryOptions = _getCacheEntryOptions.Invoke(); 64 | cacheEntry.SetOptions(cacheEntryOptions); 65 | var data = await _administrativeUnitsClient.GetMunicipalities(); 66 | 67 | if (data is null) 68 | { 69 | cacheEntry.Dispose(); 70 | return null; 71 | } 72 | 73 | return data; 74 | } 75 | ); 76 | 77 | return municipalities ?? new List(); 78 | } 79 | 80 | /// 81 | public async Task> GetMunicipalities(string countyNumber) 82 | { 83 | var counties = await GetCounties(); 84 | if (counties.FirstOrDefault(c => c.Number == countyNumber) == null) 85 | { 86 | return new List(); 87 | } 88 | 89 | var municipalities = await _memoryCache.GetOrCreateAsync( 90 | $"county-{countyNumber}-{MUNICIPALITIES_CACHE_KEY_BASE}", 91 | async cacheEntry => 92 | { 93 | var cacheEntryOptions = _getCacheEntryOptions.Invoke(); 94 | cacheEntry.SetOptions(cacheEntryOptions); 95 | 96 | var data = await _administrativeUnitsClient.GetMunicipalities(countyNumber); 97 | 98 | if (data is null) 99 | { 100 | cacheEntry.Dispose(); 101 | return null; 102 | } 103 | 104 | return data; 105 | } 106 | ); 107 | 108 | return municipalities ?? new List(); 109 | } 110 | 111 | // Expires the cache entry at midnight, to get potential new or removed entries. 112 | private static MemoryCacheEntryOptions DefaultCacheEntryOptions() 113 | { 114 | DateTime expirationTime = DateTime.Today.AddDays(1); 115 | 116 | return new MemoryCacheEntryOptions() 117 | { 118 | AbsoluteExpiration = expirationTime, 119 | Priority = CacheItemPriority.Normal, 120 | }; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/Clients/ClassificationsHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Net; 3 | using Altinn.Codelists.Extensions; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace Altinn.Codelists.SSB.Clients; 7 | 8 | /// 9 | /// Http client to get classification codes from SSB. 10 | /// 11 | internal sealed class ClassificationsHttpClient : IClassificationsClient 12 | { 13 | private readonly HttpClient _httpClient; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public ClassificationsHttpClient(IOptions settings, HttpClient httpClient) 19 | { 20 | _httpClient = httpClient; 21 | _httpClient.BaseAddress = new Uri(settings.Value.BaseApiUrl); 22 | _httpClient.DefaultRequestHeaders.Add("Accept", "application/json;charset=utf-8"); 23 | } 24 | 25 | /// 26 | /// Gets the codes for the specified classification. 27 | /// 28 | /// The id of the classification to get 29 | /// The language code used for the labels. Valid options are nb (norsk bokmål), nn (nynorsk) and en (english) 30 | /// Default if nothing is specified is nb (norsk bokmål). 31 | /// 32 | /// The date the classification should be valid 33 | /// The hierarchy level for classifications with multiple levels. Defaults to empty string, ie. all levels. 34 | /// The name of the variant to use instead of the original code list specified. 35 | /// selectCodes is used to limit the result to codes that match the pattern given by selectCodes. 36 | /// 37 | public async Task GetClassificationCodes( 38 | int classificationId, 39 | string language = "nb", 40 | DateOnly? atDate = null, 41 | string level = "", 42 | string variant = "", 43 | string selectCodes = "" 44 | ) 45 | { 46 | string selectLanguage = $"language={language}"; 47 | 48 | // If no date is specified we use todays date to get the latest classification codes. 49 | DateOnly date = atDate ?? DateOnly.FromDateTime(DateTime.Today); 50 | string selectDate = $"&date={date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}"; 51 | 52 | // No level specified means all levels will be returned 53 | string selectLevel = level == string.Empty ? string.Empty : $"&selectLevel={level}"; 54 | 55 | // Variants are referenced by name 56 | string selectVariant = variant.IsNullOrEmpty() ? string.Empty : $"&variantName={variant}"; 57 | 58 | //SelectCodes 59 | string selectedCodes = selectCodes.IsNullOrEmpty() ? string.Empty : $"&selectCodes={selectCodes}"; 60 | 61 | // Start of url differs depending on if we are getting codes or variants 62 | string url = $"{classificationId}/codesAt"; 63 | if (!variant.IsNullOrEmpty()) 64 | { 65 | url = $"{classificationId}/variantAt"; 66 | } 67 | string query = BuildQuery(selectLanguage, selectDate, selectLevel, selectVariant, selectedCodes); 68 | 69 | using var response = await _httpClient.GetAsync($"{url}{query}"); 70 | 71 | if (response.IsSuccessStatusCode) 72 | { 73 | var responseJson = await response.Content.ReadAsStringAsync(); 74 | var classificationCodes = JsonSerializer.Deserialize(responseJson); 75 | return classificationCodes ?? new ClassificationCodes(); 76 | } 77 | // If we get a 404 we try to get the codes in the fallback language (nb) 78 | else if (response.StatusCode == HttpStatusCode.NotFound && language != "nb") 79 | { 80 | string fallbackQuery = BuildQuery("language=nb", selectDate, selectLevel, selectVariant, selectedCodes); 81 | using var fallbackResponse = await _httpClient.GetAsync($"{url}{fallbackQuery}"); 82 | if (fallbackResponse.IsSuccessStatusCode) 83 | { 84 | var fallbackResponseJosn = await fallbackResponse.Content.ReadAsStringAsync(); 85 | var fallbackClassificationCodes = JsonSerializer.Deserialize(fallbackResponseJosn); 86 | return fallbackClassificationCodes ?? new ClassificationCodes(); 87 | } 88 | } 89 | 90 | return new ClassificationCodes(); 91 | } 92 | 93 | private static string BuildQuery( 94 | string selectLanguage, 95 | string selectDate, 96 | string selectLevel, 97 | string selectVariant, 98 | string selectCodes 99 | ) 100 | { 101 | return $"?{selectLanguage}{selectDate}{selectLevel}{selectVariant}{selectCodes}"; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/Posten/Clients/mappings/wiremock_GET_postnummerregister-ansi.txt.json: -------------------------------------------------------------------------------- 1 | { 2 | "Guid": "a5f1bfa5-b2fa-4293-9350-afaa58a671c4", 3 | "UpdatedAt": "2025-09-19T11:14:40.6499182Z", 4 | "Title": "Proxy Mapping for GET /postnummerregister-ansi.txt", 5 | "Description": "Proxy Mapping for GET /postnummerregister-ansi.txt", 6 | "Priority": -2000000, 7 | "Request": { 8 | "Path": { 9 | "Matchers": [ 10 | { 11 | "Name": "WildcardMatcher", 12 | "Pattern": "/postnummerregister-ansi.txt", 13 | "IgnoreCase": false 14 | } 15 | ] 16 | }, 17 | "Methods": [ 18 | "GET" 19 | ], 20 | "HttpVersion": "1.1", 21 | "Headers": [] 22 | }, 23 | "Response": { 24 | "StatusCode": 200, 25 | "BodyAsBytes": "MDAwMQlPU0xPCTAzMDEJT1NMTwlQDQowMDEwCU9TTE8JMDMwMQlPU0xPCUINCjAwMTUJT1NMTwkwMzAxCU9TTE8JQg0KMDAxOAlPU0xPCTAzMDEJT1NMTwlTDQowMDIxCU9TTE8JMDMwMQlPU0xPCVANCjAwMjQJT1NMTwkwMzAxCU9TTE8JUA0KMDAyNglPU0xPCTAzMDEJT1NMTwlCDQowMDI4CU9TTE8JMDMwMQlPU0xPCVANCjAwMzAJT1NMTwkwMzAxCU9TTE8JUA0KOTY5MQlIQVbYWVNVTkQJNTYxOAlNxVPYWQlQDQo5NjkyCU3FU9hZCTU2MTgJTcVT2FkJQg0KOTcwMAlMQUtTRUxWCTU2MjIJUE9SU0FOR0VSIFBPUlPBTkdVIFBPUlNBTktJCUcNCjk3MDkJUE9SU0FOR01PRU4JNTYyMglQT1JTQU5HRVIgUE9SU8FOR1UgUE9SU0FOS0kJRw0KOTcxMAlJTkRSRSBCSUxMRUZKT1JECTU2MjIJUE9SU0FOR0VSIFBPUlPBTkdVIFBPUlNBTktJCUcNCjk3MTEJTEFLU0VMVgk1NjIyCVBPUlNBTkdFUiBQT1JTwU5HVSBQT1JTQU5LSQlQDQo5NzEyCUxBS1NFTFYJNTYyMglQT1JTQU5HRVIgUE9SU8FOR1UgUE9SU0FOS0kJUA0KOTcxMwlSVVNTRU5FUwk1NjIyCVBPUlNBTkdFUiBQT1JTwU5HVSBQT1JTQU5LSQlHDQo5NzE0CVNORUZKT1JECTU2MTgJTcVT2FkJRw0KOTcxNQlLT0tFTFYJNTYwMwlIQU1NRVJGRVNUCUcNCjk3MTYJQthSU0VMVgk1NjIyCVBPUlNBTkdFUiBQT1JTwU5HVSBQT1JTQU5LSQlHDQo5NzE3CVZFSURORVMJNTYyNAlMRUJFU0JZCUINCjk3MjIJU0tPR0FOVkFSUkUJNTYyMglQT1JTQU5HRVIgUE9SU8FOR1UgUE9SU0FOS0kJRw0KOTczMAlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczMQlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczMglLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczMwlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczNAlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczNQlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJUA0KOTczNglLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczNwlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTc0MAlMRUJFU0JZCTU2MjQJTEVCRVNCWQlHDQo5NzQyCUtVTkVTCTU2MjQJTEVCRVNCWQlCDQo5NzUwCUhPTk5JTkdTVsVHCTU2MjAJTk9SREtBUFAJRw0KOTc1MQlIT05OSU5HU1bFRwk1NjIwCU5PUkRLQVBQCVANCjk3NjAJTk9SRFbFR0VOCTU2MjAJTk9SREtBUFAJRw0KOTc2MglLQU3YWVbGUgk1NjIwCU5PUkRLQVBQCUcNCjk3NjMJU0tBUlNWxUcJNTYyMAlOT1JES0FQUAlHDQo5NzY0CU5PUkRLQVBQCTU2MjAJTk9SREtBUFAJRw0KOTc2NQlHSkVTVsZSCTU2MjAJTk9SREtBUFAJRw0KOTc2NglTQVJORVMJNTYyMAlOT1JES0FQUAlHDQo5NzY4CVJFUFbFRwk1NjIwCU5PUkRLQVBQCUcNCjk3NzAJTUVIQU1OCTU2MjYJR0FNVklLCUINCjk3NzEJU0tKxU5FUwk1NjI2CUdBTVZJSwlHDQo5NzcyCUxBTkdGSk9SRE5FUwk1NjI2CUdBTVZJSwlHDQo5NzczCU5FUlZFSQk1NjI2CUdBTVZJSwlCDQo5Nzc1CUdBTVZJSwk1NjI2CUdBTVZJSwlCDQo5NzgyCURZRkpPUkQJNTYyNAlMRUJFU0JZCUcNCjk3OTAJS0rYTExFRkpPUkQJNTYyNAlMRUJFU0JZCUINCjk4MDAJVkFEU9gJNTYwNwlWQURT2AlHDQo5ODAxCVZBRFPYCTU2MDcJVkFEU9gJRw0KOTgwMglWRVNUUkUgSkFLT0JTRUxWCTU2MDcJVkFEU9gJRw0KOTgwMwlWQURT2Ak1NjA3CVZBRFPYCUcNCjk4MDQJVkFEU9gJNTYwNwlWQURT2AlHDQo5ODEwCVZFU1RSRSBKQUtPQlNFTFYJNTYwNwlWQURT2AlQDQo5ODExCVZBRFPYCTU2MDcJVkFEU9gJUA0KOTgxNQlWQURT2Ak1NjA3CVZBRFPYCVANCjk4MjAJVkFSQU5HRVJCT1ROCTU2MzYJVU5KQVJHR0EgTkVTU0VCWQlQDQo5ODI2CVNJUk1BCTU2MjgJREVBVE5VIFRBTkEJRw0KOTg0MAlWQVJBTkdFUkJPVE4JNTYzNglVTkpBUkdHQSBORVNTRUJZCUcNCjk4NDEJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDIJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDMJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDQJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDUJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDYJVEFOQQk1NjI4CURFQVROVSBUQU5BCVANCjk5MDAJS0lSS0VORVMJNTYwNQlT2FItVkFSQU5HRVIJRw0KOTkwMQlLSVJLRU5FUwk1NjA1CVPYUi1WQVJBTkdFUglHDQo5OTEwCUJK2FJORVZBVE4JNTYwNQlT2FItVkFSQU5HRVIJRw0KOTkxMQlKQVJGSk9SRAk1NjA1CVPYUi1WQVJBTkdFUglHDQo5OTEyCUhFU1NFTkcJNTYwNQlT2FItVkFSQU5HRVIJRw0KOTkxNAlCSthSTkVWQVROCTU2MDUJU9hSLVZBUkFOR0VSCVANCjk5MTUJS0lSS0VORVMJNTYwNQlT2FItVkFSQU5HRVIJUA0KOTkxNglIRVNTRU5HCTU2MDUJU9hSLVZBUkFOR0VSCVANCjk5MTcJS0lSS0VORVMJNTYwNQlT2FItVkFSQU5HRVIJUA0KOTkyNQlTVkFOVklLCTU2MDUJU9hSLVZBUkFOR0VSCUcNCjk5MjYJUEFTVklLCTU2MDUJU9hSLVZBUkFOR0VSCUcNCjk5MzAJTkVJREVOCTU2MDUJU9hSLVZBUkFOR0VSCUcNCjk5MzUJQlVH2FlORVMJNTYwNQlT2FItVkFSQU5HRVIJQg0KOTk1MAlWQVJE2Ak1NjM0CVZBUkTYCUcNCjk5NTEJVkFSRNgJNTYzNAlWQVJE2AlQDQo5OTUyCVZBUkTYCTU2MzQJVkFSRNgJRw0KOTk1NQlTVkFSVE5FUwk1NjM0CVZBUkTYCUcNCjk5NjAJS0lCRVJHCTU2MzQJVkFSRNgJRw0KOTk4MAlCRVJMRVbFRwk1NjMwCUJFUkxFVsVHCUcNCjk5ODEJQkVSTEVWxUcJNTYzMAlCRVJMRVbFRwlQDQo5OTgyCUtPTkdTRkpPUkQJNTYzMAlCRVJMRVbFRwlHDQo5OTkwCULFVFNGSk9SRAk1NjMyCULFVFNGSk9SRAlHDQo5OTkxCULFVFNGSk9SRAk1NjMyCULFVFNGSk9SRAlQDQo=", 26 | "Headers": { 27 | "Content-Length": "2897", 28 | "Content-Type": "text/plain; charset=iso-8859-1", 29 | "Connection": "keep-alive", 30 | "Server": "Apache", 31 | "X-XSS-Protection": "1; mode=block", 32 | "cache-status": "Booster; fwd=miss", 33 | "X-Frame-Options": "DENY", 34 | "x-xp-app": "no.posten.mapextended", 35 | "X-Content-Type-Options": "nosniff", 36 | "Via": [ 37 | "1.1 google", 38 | "1.1 varnish" 39 | ], 40 | "Accept-Ranges": "bytes", 41 | "Date": "Fri, 19 Sep 2025 11:14:40 GMT", 42 | "X-Served-By": "cache-osl6526-OSL, cache-osl6531-OSL", 43 | "X-Cache": "MISS, MISS", 44 | "X-Cache-Hits": "0, 0", 45 | "X-Timer": "S1758280481.521525,VS0,VE104", 46 | "Vary": "Accept-Encoding", 47 | "Strict-Transport-Security": "max-age=31557600", 48 | "Alt-Svc": [ 49 | "h3=\":443\"", 50 | "h3-29=\":443\"", 51 | "h3-27=\":443\"" 52 | ] 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Mocks/ClassificationsHttpClientMock.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Codelists.SSB; 2 | using Altinn.Codelists.SSB.Clients; 3 | using RichardSzalay.MockHttp; 4 | 5 | namespace Altinn.Codelists.Tests.SSB.Mocks; 6 | 7 | public class ClassificationsHttpClientMock : IClassificationsClient 8 | { 9 | private const string SEX_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.sex.json"; 10 | private const string INDUSTRY_GROUPING_TESTDATA_RESOURCE = 11 | "Altinn.Codelists.Tests.SSB.Testdata.industryGrouping.json"; 12 | private const string OCCUPATIONS_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.occupations.json"; 13 | private const string MARITAL_STATUS_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.maritalStatus.json"; 14 | private const string BASE_AMOUT_NATIONAL_INSURANCE_TESTDATA_RESOURCE = 15 | "Altinn.Codelists.Tests.SSB.Testdata.baseAmountNationalInsurance.json"; 16 | private const string COUNTIES_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.counties.json"; 17 | private const string MUNICIPALITIES_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.municipalities.json"; 18 | private const string COUNTRIES_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.countries.json"; 19 | private const string SMALL_GAME_VARIANT_TESTDATA_RESOURCE = 20 | "Altinn.Codelists.Tests.SSB.Testdata.smallGame_Variant.json"; 21 | private const string UNITS_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.units.json"; 22 | 23 | private readonly IClassificationsClient _client; 24 | private readonly IOptions _options; 25 | 26 | public MockHttpMessageHandler HttpMessageHandlerMock { get; private set; } 27 | public MockedRequest MockedClassificationsRequest { get; private set; } 28 | public MockedRequest MockedMaritalStatusRequest { get; private set; } 29 | 30 | public ClassificationsHttpClientMock(IOptions classificationOptions) 31 | { 32 | _options = classificationOptions; 33 | 34 | HttpMessageHandlerMock = new MockHttpMessageHandler(); 35 | 36 | HttpMessageHandlerMock 37 | .When("https://data.ssb.no/api/klass/v1/classifications/2/*") 38 | .Respond("application/json", EmbeddedResource.LoadDataAsString(SEX_TESTDATA_RESOURCE).Result); 39 | 40 | HttpMessageHandlerMock 41 | .When("https://data.ssb.no/api/klass/v1/classifications/6/*") 42 | .Respond("application/json", EmbeddedResource.LoadDataAsString(INDUSTRY_GROUPING_TESTDATA_RESOURCE).Result); 43 | 44 | HttpMessageHandlerMock 45 | .When("https://data.ssb.no/api/klass/v1/classifications/7/*") 46 | .Respond("application/json", EmbeddedResource.LoadDataAsString(OCCUPATIONS_TESTDATA_RESOURCE).Result); 47 | 48 | MockedMaritalStatusRequest = HttpMessageHandlerMock 49 | .When("https://data.ssb.no/api/klass/v1/classifications/19/*") 50 | .Respond("application/json", EmbeddedResource.LoadDataAsString(MARITAL_STATUS_TESTDATA_RESOURCE).Result); 51 | 52 | MockedClassificationsRequest = HttpMessageHandlerMock 53 | .When("https://data.ssb.no/api/klass/v1/classifications/20/*") 54 | .Respond( 55 | "application/json", 56 | EmbeddedResource.LoadDataAsString(BASE_AMOUT_NATIONAL_INSURANCE_TESTDATA_RESOURCE).Result 57 | ); 58 | 59 | HttpMessageHandlerMock 60 | .When("https://data.ssb.no/api/klass/v1/classifications/104/*") 61 | .Respond("application/json", EmbeddedResource.LoadDataAsString(COUNTIES_TESTDATA_RESOURCE).Result); 62 | HttpMessageHandlerMock 63 | .When("https://data.ssb.no/api/klass/v1/classifications/131/*") 64 | .Respond("application/json", EmbeddedResource.LoadDataAsString(MUNICIPALITIES_TESTDATA_RESOURCE).Result); 65 | 66 | HttpMessageHandlerMock 67 | .When("https://data.ssb.no/api/klass/v1/classifications/552/*") 68 | .Respond("application/json", EmbeddedResource.LoadDataAsString(COUNTRIES_TESTDATA_RESOURCE).Result); 69 | 70 | HttpMessageHandlerMock 71 | .When("https://data.ssb.no/api/klass/v1/classifications/74/variantAt*") 72 | .Respond( 73 | "application/json", 74 | EmbeddedResource.LoadDataAsString(SMALL_GAME_VARIANT_TESTDATA_RESOURCE).Result 75 | ); 76 | 77 | HttpMessageHandlerMock 78 | .When("https://data.ssb.no/api/klass/v1/classifications/303/*") 79 | .Respond("application/json", EmbeddedResource.LoadDataAsString(UNITS_TESTDATA_RESOURCE).Result); 80 | 81 | _client = new ClassificationsHttpClient(_options, new HttpClient(HttpMessageHandlerMock)); 82 | } 83 | 84 | public async Task GetClassificationCodes( 85 | int classificationId, 86 | string language = "nb", 87 | DateOnly? atDate = null, 88 | string level = "", 89 | string variant = "", 90 | string selectCodes = "" 91 | ) 92 | { 93 | ClassificationCodes classificationCodes = await _client.GetClassificationCodes( 94 | classificationId, 95 | language, 96 | atDate, 97 | level, 98 | variant, 99 | selectCodes 100 | ); 101 | 102 | return level == string.Empty 103 | ? classificationCodes 104 | : new ClassificationCodes() 105 | { 106 | Codes = classificationCodes.Codes.Where(x => x.Level == level.ToString()).ToList(), 107 | }; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | *.received.txt 353 | *.received.json 354 | -------------------------------------------------------------------------------- /src/Altinn.Codelists/SSB/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Altinn.App.Core.Features; 2 | using Altinn.Codelists.SSB.Clients; 3 | using Altinn.Codelists.SSB.Models; 4 | using Microsoft.Extensions.Caching.Memory; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Altinn.Codelists.SSB.Extensions; 8 | 9 | /// 10 | /// Extends the . 11 | /// 12 | public static class ServiceCollectionExtensions 13 | { 14 | /// 15 | /// Registers the services required to get support for SSB Classifiations. 16 | /// 17 | public static IServiceCollection AddSSBClassifications(this IServiceCollection services) 18 | { 19 | EnsureBasicServicesRegistered(services); 20 | 21 | // Add the codelist providers 22 | services.AddSSBClassificationCodelistProvider("kjønn", Classification.Sex); 23 | services.AddSSBClassificationCodelistProvider("næringsgruppering", Classification.IndustryGrouping); 24 | services.AddSSBClassificationCodelistProvider("yrker", Classification.Occupations); 25 | services.AddSSBClassificationCodelistProvider("sivilstand", Classification.MaritalStatus); 26 | services.AddSSBClassificationCodelistProvider( 27 | "grunnbeløpfolketrygden", 28 | Classification.BaseAmountNationalInsurance 29 | ); 30 | services.AddSSBClassificationCodelistProvider("fylker", Classification.Counties); 31 | services.AddSSBClassificationCodelistProvider("kommuner", Classification.Municipalities); 32 | services.AddSSBClassificationCodelistProvider("land", Classification.Countries); 33 | 34 | return services; 35 | } 36 | 37 | // Services added here should be safe to add multiple times 38 | // either resulting in singleton or replacing the existing service 39 | private static void EnsureBasicServicesRegistered(IServiceCollection services) 40 | { 41 | services.AddMemoryCache(); 42 | services.AddOptions(); 43 | 44 | if (!services.Any(x => x.ServiceType == typeof(IClassificationsClient))) 45 | { 46 | services.AddHttpClient(); 47 | services.AddTransient(sp => new ClassificationsHttpClientCached( 48 | ActivatorUtilities.CreateInstance(sp), 49 | sp.GetRequiredService() 50 | )); 51 | } 52 | } 53 | 54 | /// 55 | /// Adds the specified as an with the specified id. 56 | /// 57 | /// The to add to 58 | /// The codelist id 59 | /// The to return 60 | /// Default set of key/value pairs to be used. Will be overriden by matching qyery parameters runtime. 61 | #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters 62 | public static IServiceCollection AddSSBClassificationCodelistProvider( 63 | this IServiceCollection services, 64 | string id, 65 | Classification classification, 66 | Dictionary? defaultKeyValuePairs = null 67 | ) 68 | { 69 | EnsureBasicServicesRegistered(services); 70 | services.AddTransient(sp => new ClassificationCodelistProvider( 71 | id, 72 | classification, 73 | sp.GetRequiredService(), 74 | defaultKeyValuePairs 75 | )); 76 | 77 | return services; 78 | } 79 | 80 | /// 81 | /// Adds the specified classification based on the known classification id. If it is an id mapped to the enum 82 | /// the correct enum will be set, otherwise Custom will be used as enum value but the id will be sent to the underlying api. 83 | /// 84 | /// The to add to 85 | /// The codelist id 86 | /// The to return 87 | /// allowing control over how data maps from the source to the app options 88 | /// Default set of key/value pairs to be used. Will be overriden by matching qyery parameters runtime. 89 | public static IServiceCollection AddSSBClassificationCodelistProvider( 90 | this IServiceCollection services, 91 | string id, 92 | Classification classification, 93 | ClassificationOptions options, 94 | Dictionary? defaultKeyValuePairs = null 95 | ) 96 | { 97 | EnsureBasicServicesRegistered(services); 98 | services.AddTransient(sp => new ClassificationCodelistProvider( 99 | id, 100 | classification, 101 | sp.GetRequiredService(), 102 | options, 103 | defaultKeyValuePairs 104 | )); 105 | 106 | return services; 107 | } 108 | 109 | /// 110 | /// Adds the specified classification based on the known classification id. If it is an id mapped to the enum 111 | /// the correct enum will be set, otherwise Custom will be used as enum value but the id will be sent to the underlying api. 112 | /// 113 | /// The to add to 114 | /// The codelist id 115 | /// The id of the classification to return 116 | /// Default set of key/value pairs to be used. Will be overriden by matching qyery parameters runtime. 117 | public static IServiceCollection AddSSBClassificationCodelistProvider( 118 | this IServiceCollection services, 119 | string id, 120 | int classificationId, 121 | Dictionary? defaultKeyValuePairs = null 122 | ) 123 | { 124 | EnsureBasicServicesRegistered(services); 125 | services.AddTransient(sp => new ClassificationCodelistProvider( 126 | id, 127 | classificationId, 128 | sp.GetRequiredService(), 129 | defaultKeyValuePairs 130 | )); 131 | 132 | return services; 133 | } 134 | 135 | /// 136 | /// Adds the specified classification based on the known classification id. If it is an id mapped to the enum 137 | /// the correct enum will be set, otherwise Custom will be used as enum value but the id will be sent to the underlying api. 138 | /// 139 | /// The to add to 140 | /// The codelist id 141 | /// The id of the classification to return 142 | /// allowing control over how data maps from the source to the app options 143 | /// Default set of key/value pairs to be used. Will be overriden by matching qyery parameters runtime. 144 | public static IServiceCollection AddSSBClassificationCodelistProvider( 145 | #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters 146 | 147 | this IServiceCollection services, 148 | string id, 149 | int classificationId, 150 | ClassificationOptions options, 151 | Dictionary? defaultKeyValuePairs = null 152 | ) 153 | { 154 | EnsureBasicServicesRegistered(services); 155 | services.AddTransient(sp => new ClassificationCodelistProvider( 156 | id, 157 | classificationId, 158 | sp.GetRequiredService(), 159 | defaultKeyValuePairs, 160 | options 161 | )); 162 | 163 | return services; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | # Non-configurable behaviors 3 | charset = utf-8 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | 7 | # Configurable behaviors 8 | # end_of_line = lf - there is no 'auto' with a .editorconfig 9 | indent_style = space 10 | indent_size = 4 11 | max_line_length = 120 12 | 13 | # XML project files 14 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 15 | indent_size = 2 16 | 17 | # XML config files 18 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 19 | indent_size = 2 20 | 21 | [*.{cs,vb}] 22 | dotnet_sort_system_directives_first = true 23 | dotnet_separate_import_directive_groups = false 24 | 25 | end_of_line = crlf 26 | 27 | #### Naming styles #### 28 | 29 | # Interfaces start with 'I' 30 | dotnet_naming_symbols.interface.applicable_kinds = interface 31 | dotnet_naming_style.begins_with_i.required_prefix = I 32 | dotnet_naming_style.begins_with_i.required_suffix = 33 | dotnet_naming_style.begins_with_i.word_separator = 34 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 35 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning 36 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 37 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 38 | 39 | # Types should be PascalCase 40 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum, namespace, delegate 41 | dotnet_naming_rule.types_should_be_pascal_case.severity = warning 42 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 43 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 44 | 45 | # Non-private members should be PascalCase 46 | dotnet_naming_symbols.non_private_members.applicable_kinds = property, event, field 47 | dotnet_naming_symbols.non_private_members.applicable_accessibilities = public, internal, protected, protected_internal 48 | dotnet_naming_rule.non_private_members_should_be_pascal_case.severity = warning 49 | dotnet_naming_rule.non_private_members_should_be_pascal_case.symbols = non_private_members 50 | dotnet_naming_rule.non_private_members_should_be_pascal_case.style = pascal_case 51 | 52 | # All methods should be PascalCase 53 | dotnet_naming_symbols.methods.applicable_kinds = method, local_function 54 | dotnet_naming_rule.methods_should_be_pascal_case.severity = warning 55 | dotnet_naming_rule.methods_should_be_pascal_case.symbols = methods 56 | dotnet_naming_rule.methods_should_be_pascal_case.style = pascal_case 57 | 58 | # Private member should be '_' prefixed and camelCase 59 | dotnet_naming_symbols.private_members.applicable_kinds = property, event, field 60 | dotnet_naming_symbols.private_members.applicable_accessibilities = private, private_protected 61 | dotnet_naming_style.__prefixed_camel_case.required_prefix = _ 62 | dotnet_naming_style.__prefixed_camel_case.required_suffix = 63 | dotnet_naming_style.__prefixed_camel_case.word_separator = 64 | dotnet_naming_style.__prefixed_camel_case.capitalization = camel_case 65 | dotnet_naming_rule.private_members_should_be___prefixed_camel_case.severity = warning 66 | dotnet_naming_rule.private_members_should_be___prefixed_camel_case.symbols = private_members 67 | dotnet_naming_rule.private_members_should_be___prefixed_camel_case.style = __prefixed_camel_case 68 | 69 | # Const fields should be PascalCase 70 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning 71 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 72 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case 73 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 74 | dotnet_naming_symbols.constant_fields.required_modifiers = const 75 | 76 | 77 | # General naming styles 78 | 79 | dotnet_naming_style.pascal_case.required_prefix = 80 | dotnet_naming_style.pascal_case.required_suffix = 81 | dotnet_naming_style.pascal_case.word_separator = 82 | dotnet_naming_style.pascal_case.capitalization = pascal_case 83 | 84 | dotnet_style_coalesce_expression = true:suggestion 85 | dotnet_style_null_propagation = true:suggestion 86 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 87 | dotnet_style_prefer_auto_properties = true:silent 88 | dotnet_style_object_initializer = true:suggestion 89 | dotnet_style_collection_initializer = true:suggestion 90 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 91 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 92 | 93 | csharp_using_directive_placement = outside_namespace:silent 94 | csharp_prefer_simple_using_statement = true:suggestion 95 | csharp_prefer_braces = true:silent 96 | csharp_style_prefer_method_group_conversion = true:silent 97 | csharp_style_prefer_top_level_statements = true:silent 98 | csharp_style_expression_bodied_methods = false:silent 99 | csharp_style_expression_bodied_constructors = false:silent 100 | csharp_style_expression_bodied_operators = false:silent 101 | csharp_style_expression_bodied_properties = true:silent 102 | csharp_style_expression_bodied_indexers = true:silent 103 | csharp_style_expression_bodied_accessors = true:silent 104 | csharp_style_expression_bodied_lambdas = true:silent 105 | csharp_style_expression_bodied_local_functions = false:silent 106 | csharp_indent_labels = one_less_than_current 107 | csharp_style_prefer_primary_constructors = false:suggestion 108 | resharper_convert_to_primary_constructor_highlighting = none 109 | csharp_style_namespace_declarations = file_scoped:error 110 | 111 | # Naming rule violation 112 | dotnet_diagnostic.IDE1006.severity = error 113 | 114 | # Unused usings 115 | dotnet_diagnostic.IDE0005.severity = warning 116 | 117 | dotnet_diagnostic.CA1825.severity = warning 118 | 119 | # IDE0052: Remove unread private member 120 | dotnet_diagnostic.IDE0052.severity = warning 121 | 122 | # CA1848: Use the LoggerMessage delegates 123 | dotnet_diagnostic.CA1848.severity = none 124 | 125 | # CA1727: Use PascalCase for named placeholders 126 | dotnet_diagnostic.CA1727.severity = suggestion 127 | 128 | # CA2254: Template should be a static expression 129 | dotnet_diagnostic.CA2254.severity = none 130 | 131 | # CA1822: Mark members as static 132 | dotnet_diagnostic.CA1822.severity = suggestion 133 | 134 | # IDE0080: Remove unnecessary suppression operator 135 | dotnet_diagnostic.IDE0080.severity = error 136 | 137 | # CA1859: Use concrete types when possible for improved performance 138 | dotnet_diagnostic.CA1859.severity = suggestion 139 | 140 | # CA1716: Rename namespace "" so that it no longer conflicts with the reserved language keyword 'Interface' 141 | # TODO: fixing this would be breaking 142 | dotnet_diagnostic.CA1716.severity = suggestion 143 | 144 | # CA1805: Do not initialize unnecessarily 145 | dotnet_diagnostic.CA1805.severity = suggestion 146 | 147 | # CA1711: Identifiers should not have incorrect suffix 148 | # TODO: fixing this would be breaking 149 | dotnet_diagnostic.CA1711.severity = suggestion 150 | 151 | # CA2201: Do not raise reserved exception types 152 | dotnet_diagnostic.CA2201.severity = suggestion 153 | 154 | # CA1720: Identifier contains type name 155 | # TODO: fixing this would be breaking 156 | dotnet_diagnostic.CA1720.severity = suggestion 157 | 158 | # CA1816: Call GC.SuppressFinalize correctly 159 | dotnet_diagnostic.CA1816.severity = warning 160 | 161 | # CA1707: Identifiers should not contain underscores 162 | dotnet_diagnostic.CA1707.severity = none 163 | 164 | # S2325: Methods and properties that don't access instance data should be static 165 | dotnet_diagnostic.S2325.severity = suggestion 166 | 167 | # S3267: Loops should be simplified with "LINQ" expressions 168 | dotnet_diagnostic.S3267.severity = suggestion 169 | 170 | # S125: Sections of code should not be commented out 171 | dotnet_diagnostic.S125.severity = suggestion 172 | 173 | # S1075: URIs should not be hardcoded 174 | dotnet_diagnostic.S1075.severity = suggestion 175 | 176 | [Program.cs] 177 | dotnet_diagnostic.CA1050.severity = none 178 | dotnet_diagnostic.S1118.severity = none 179 | 180 | [*.{yml,yaml}] 181 | indent_size = 2 182 | end_of_line = lf 183 | 184 | # Verify settings 185 | # https://github.com/VerifyTests/Verify?tab=readme-ov-file#editorconfig-settings 186 | 187 | [*.{received,verified}.{json,txt,xml}] 188 | charset = "utf-8-bom" 189 | end_of_line = lf 190 | indent_size = unset 191 | indent_style = unset 192 | insert_final_newline = false 193 | tab_width = unset 194 | trim_trailing_whitespace = false 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Altinn Common Codelists 2 | ![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/altinn/codelists-lib-dotnet) 3 | ![GitHub last commit](https://img.shields.io/github/last-commit/altinn/codelists-lib-dotnet) 4 | ![GitHub](https://img.shields.io/github/license/altinn/codelists-lib-dotnet) 5 | 6 | This library contains common code lists for use in Altinn 3 based applications. 7 | 8 | The [Getting started](#getting-started) section describes the basic steps required in order to use this package within an Altinn 3 application. For a more comprehensive description of code lists in Altinn 3 please see https://docs.altinn.studio/app/development/data/options/. 9 |
10 |
11 | 12 | ## Getting started using the package 13 | 14 | This guide assumes you have an existing Altinn 3 application. If not please see https://docs.altinn.studio/app/getting-started/create-app/ to get started. 15 | 16 | ### 1. Add reference to [Altinn.Codelists nuget package](https://www.nuget.org/packages/Altinn.Codelists) 17 | Open command line to the repo of your application and navigate to the App folder where the App.csproj file is located and run the following command: 18 | 19 | ```shell 20 | dotnet add package Altinn.Codelists 21 | ``` 22 | This will add the latest stable version of the package to your solution. 23 | 24 | As an alternative you can edit your applications App.csproj file directly by adding the reference below to the `` where you have package references. 25 | ```xml 26 | 27 | ``` 28 | Note that you then need to explicitly specify the version you would like. See the link on step one for available versions. 29 | 30 | ### 2. Register the codelists in your app DI container 31 | Add the following to your Program.cs file: 32 | ```csharp 33 | services.AddAltinnCodelists(); 34 | ``` 35 | By calling this you will register all codelists accross all sources listed below in available codelists. You can also register codelists one by one if you for example would like to provide your own codelist id, or if you would like to control the mappings to description and help texts. 36 | 37 | ### 3. Connect your application to the codelist you would like to use 38 | See the section below for available codelist id's. 39 | 40 | You can either do this using [Altinn Studio](https://altinn.studio) and configure the *Kodeliste-ID* of your component in the UI. 41 | 42 | Or you can configure the component by editing the optionsId property in FormLayout.json according to the [documentation](https://docs.altinn.studio/app/development/data/options/#connect-the-component-to-options-code-list) 43 | 44 | ## Custom configuration 45 | While the above mentioned configuration where you call `services.AddAltinnCodelists();` will add all available codelists with default values, there are cases where you might want to customize the configuration. The examples will vary a bit depending on the source of the codelist. 46 | 47 | ### Add a codelist with custom codelist id 48 | If you don't want to use the default codelist id, or only want to register codelists relevant for you app you can register each codelist individually. 49 | 50 | Example using codelist from SSB overriding the option id: 51 | ```csharp 52 | services.AddSSBClassificationCodelistProvider("næring", Classification.IndustryGrouping); 53 | ``` 54 | 55 | ### Add a codelist with default parameters 56 | Some of the codelists accepts parameters controlling what's returned. 57 | 58 | Example using the codelist from SSB specifiying a level filter to only get values from the first level (this particular codelist is hierarchical). 59 | 60 | ```csharp 61 | services.AddSSBClassificationCodelistProvider("næring", Classification.IndustryGrouping, new Dictionary() { { "level", "1" } }); 62 | ``` 63 | The default parameters is a name/value pair collection allowing for any parameter to be passed in and picked up by the implementation of the codelist provider. 64 | 65 | ### Add a codelist that has support for description and/or help text values 66 | While a regular codelist is only key/value pairs, you can extend this with adding description and help text allowing for a more descriptive UI. 67 | 68 | The following example enables the notes field from SSB classification to populate the description text. 69 | 70 | ```csharp 71 | services.AddSSBClassificationCodelistProvider("næring", Classification.IndustryGrouping, 72 | new ClassificationOptions() { MapNotesToDescription = true }, 73 | new Dictionary() { { "level", "1" } }); 74 | ``` 75 | The above example enables a predefined way of adding a description text. If you would like to customize the description text even further you can pass inn a function. 76 | The follwing examples passes in a function that that will be evaluated when populating the code list and will return a combination of the classification code and the notes fields separated by colon. 77 | 78 | ```csharp 79 | services.AddSSBClassificationCodelistProvider( 80 | "næring", 81 | Classification.IndustryGrouping, 82 | new ClassificationOptions() 83 | { 84 | MapDescriptionFunc = (classificationCode) => $"{classificationCode.Code}: {classificationCode.Notes}" 85 | }, 86 | new Dictionary() { { "level", "1" } }); 87 | ``` 88 | 89 | ### Add a codelist from SSB not available in the `Classification` enum. 90 | Currently only a small subset of the available codelist from SSB is included in the `Classification` enum. The enum is really only provided as a more readable version of the underlying id provided by SSB. But in our case also serves as a way of telling what codelists we have tested explicitly against. If you find a codelist you would like to use, you can specify it's id instead of the enum. 91 | 92 | ```csharp 93 | services.AddSSBClassificationCodelistProvider("næring", 6); 94 | ``` 95 | 96 | 97 | ## Available codelists 98 | The list below shows currently implemented code lists with their default id. 99 | 100 | | Default Codelist Id | Source | Description | 101 | |------------------------- | ------------ | --------------------------------------------------------- | 102 | | fylker | SSB | The counties of Norway | 103 | | fylker-kv | Kartverket | The counties of Norway | 104 | | grunnbeløpfolketrygden | SSB | National insurance base amount | 105 | | kjønn | SSB | Sex | 106 | | kommuner | SSB | The communes of Norway (all) | 107 | | kommuner-kv | Kartverket | The communes of Norway with ability to filter on county | 108 | | land | SSB | The countries of the world | 109 | | næringsgruppering | SSB | Industrical grouping | 110 | | poststed | Posten | Norwegian postal codes | 111 | | sivilstand | SSB | Marital status | 112 | | yrker | SSB | Occupations | 113 | 114 | 115 | 116 | ## Sources 117 | Below are the sources used for the various codelists above. The underlying api's provide different functionality with regards to query parameters. Espessialy the SSB api's provide a rich set of parameters allowing the query for valid values on a given date or date range. 118 | 119 | ### [Statistisk Sentralbyrå/SSB](https://www.ssb.no/) 120 | Doc: https://data.ssb.no/api/klass/v1/api-guide.html 121 | 122 | Licence: https://data.ssb.no/api/klass/v1/api-guide.html#_license 123 | 124 | ### [Kartverket/KV](https://www.kartverket.no/) 125 | Doc: https://kartkatalog.geonorge.no/metadata/administrative-enheter-kommuner/041f1e6e-bdbc-4091-b48f-8a5990f3cc5b 126 | 127 | Api: https://ws.geonorge.no/kommuneinfo/v1/ 128 | 129 | Licence: https://www.kartverket.no/api-og-data/vilkar-for-bruk 130 | 131 | ### [Posten](https://www.bring.no) 132 | Doc: https://www.bring.no/tjenester/adressetjenester/postnummer/postnummertabeller-veiledning 133 | 134 | ## Contributing 135 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 136 | ## Authors 137 | Altinn Apps development team - If you want to get in touch, just create a new issue. 138 | See also the list of [contributors](https://github.com/Altinn/codelists-lib-dotnet/graphs/contributors) who participated in this project. 139 | ## License 140 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. 141 | -------------------------------------------------------------------------------- /test/Altinn.Codelists.Tests/SSB/Testdata/baseAmountNationalInsurance.json: -------------------------------------------------------------------------------- 1 | { 2 | "codes": [ 3 | { 4 | "code": "1991", 5 | "parentCode": null, 6 | "level": "1", 7 | "name": "35 500 kroner", 8 | "shortName": "", 9 | "presentationName": "", 10 | "validFrom": null, 11 | "validTo": null, 12 | "notes": "" 13 | }, 14 | { 15 | "code": "1992", 16 | "parentCode": null, 17 | "level": "1", 18 | "name": "36 500 kroner", 19 | "shortName": "", 20 | "presentationName": "", 21 | "validFrom": null, 22 | "validTo": null, 23 | "notes": "" 24 | }, 25 | { 26 | "code": "1993", 27 | "parentCode": null, 28 | "level": "1", 29 | "name": "37 300 kroner", 30 | "shortName": "", 31 | "presentationName": "", 32 | "validFrom": null, 33 | "validTo": null, 34 | "notes": "" 35 | }, 36 | { 37 | "code": "1994", 38 | "parentCode": null, 39 | "level": "1", 40 | "name": "38 080 kroner", 41 | "shortName": "", 42 | "presentationName": "", 43 | "validFrom": null, 44 | "validTo": null, 45 | "notes": "" 46 | }, 47 | { 48 | "code": "1995", 49 | "parentCode": null, 50 | "level": "1", 51 | "name": "39 230 kroner", 52 | "shortName": "", 53 | "presentationName": "", 54 | "validFrom": null, 55 | "validTo": null, 56 | "notes": "" 57 | }, 58 | { 59 | "code": "1996", 60 | "parentCode": null, 61 | "level": "1", 62 | "name": "41 000 kroner", 63 | "shortName": "", 64 | "presentationName": "", 65 | "validFrom": null, 66 | "validTo": null, 67 | "notes": "" 68 | }, 69 | { 70 | "code": "1997", 71 | "parentCode": null, 72 | "level": "1", 73 | "name": "42 500 kroner", 74 | "shortName": "", 75 | "presentationName": "", 76 | "validFrom": null, 77 | "validTo": null, 78 | "notes": "" 79 | }, 80 | { 81 | "code": "1998", 82 | "parentCode": null, 83 | "level": "1", 84 | "name": "45 370 kroner", 85 | "shortName": "", 86 | "presentationName": "", 87 | "validFrom": null, 88 | "validTo": null, 89 | "notes": "" 90 | }, 91 | { 92 | "code": "1999", 93 | "parentCode": null, 94 | "level": "1", 95 | "name": "46 950 kroner", 96 | "shortName": "", 97 | "presentationName": "", 98 | "validFrom": null, 99 | "validTo": null, 100 | "notes": "" 101 | }, 102 | { 103 | "code": "2000", 104 | "parentCode": null, 105 | "level": "1", 106 | "name": "49 090 kroner", 107 | "shortName": "", 108 | "presentationName": "", 109 | "validFrom": null, 110 | "validTo": null, 111 | "notes": "" 112 | }, 113 | { 114 | "code": "2001", 115 | "parentCode": null, 116 | "level": "1", 117 | "name": "51 360 kroner", 118 | "shortName": "", 119 | "presentationName": "", 120 | "validFrom": null, 121 | "validTo": null, 122 | "notes": "" 123 | }, 124 | { 125 | "code": "2002", 126 | "parentCode": null, 127 | "level": "1", 128 | "name": "54 170 kroner", 129 | "shortName": "", 130 | "presentationName": "", 131 | "validFrom": null, 132 | "validTo": null, 133 | "notes": "" 134 | }, 135 | { 136 | "code": "2003", 137 | "parentCode": null, 138 | "level": "1", 139 | "name": "56 861 kroner", 140 | "shortName": "", 141 | "presentationName": "", 142 | "validFrom": null, 143 | "validTo": null, 144 | "notes": "" 145 | }, 146 | { 147 | "code": "2004", 148 | "parentCode": null, 149 | "level": "1", 150 | "name": "58 778 kroner", 151 | "shortName": "", 152 | "presentationName": "", 153 | "validFrom": null, 154 | "validTo": null, 155 | "notes": "" 156 | }, 157 | { 158 | "code": "2005", 159 | "parentCode": null, 160 | "level": "1", 161 | "name": "60 699 kroner", 162 | "shortName": "", 163 | "presentationName": "", 164 | "validFrom": null, 165 | "validTo": null, 166 | "notes": "" 167 | }, 168 | { 169 | "code": "2006", 170 | "parentCode": null, 171 | "level": "1", 172 | "name": "62 892 kroner", 173 | "shortName": "", 174 | "presentationName": "", 175 | "validFrom": null, 176 | "validTo": null, 177 | "notes": "" 178 | }, 179 | { 180 | "code": "2007", 181 | "parentCode": null, 182 | "level": "1", 183 | "name": "66 812 kroner", 184 | "shortName": "", 185 | "presentationName": "", 186 | "validFrom": null, 187 | "validTo": null, 188 | "notes": "" 189 | }, 190 | { 191 | "code": "2008", 192 | "parentCode": null, 193 | "level": "1", 194 | "name": "70 256 kroner", 195 | "shortName": "", 196 | "presentationName": "", 197 | "validFrom": null, 198 | "validTo": null, 199 | "notes": "" 200 | }, 201 | { 202 | "code": "2009", 203 | "parentCode": null, 204 | "level": "1", 205 | "name": "72 881 kroner", 206 | "shortName": "", 207 | "presentationName": "", 208 | "validFrom": null, 209 | "validTo": null, 210 | "notes": "" 211 | }, 212 | { 213 | "code": "2010", 214 | "parentCode": null, 215 | "level": "1", 216 | "name": "75 641 kroner", 217 | "shortName": "", 218 | "presentationName": "", 219 | "validFrom": null, 220 | "validTo": null, 221 | "notes": "" 222 | }, 223 | { 224 | "code": "2011", 225 | "parentCode": null, 226 | "level": "1", 227 | "name": "79 216 kroner", 228 | "shortName": "", 229 | "presentationName": "", 230 | "validFrom": null, 231 | "validTo": null, 232 | "notes": "" 233 | }, 234 | { 235 | "code": "2012", 236 | "parentCode": null, 237 | "level": "1", 238 | "name": "82 122 kroner", 239 | "shortName": "", 240 | "presentationName": "", 241 | "validFrom": null, 242 | "validTo": null, 243 | "notes": "" 244 | }, 245 | { 246 | "code": "2013", 247 | "parentCode": null, 248 | "level": "1", 249 | "name": "85 245 kroner", 250 | "shortName": "", 251 | "presentationName": "", 252 | "validFrom": null, 253 | "validTo": null, 254 | "notes": "" 255 | }, 256 | { 257 | "code": "2014", 258 | "parentCode": null, 259 | "level": "1", 260 | "name": "88 370 kroner", 261 | "shortName": "", 262 | "presentationName": "", 263 | "validFrom": null, 264 | "validTo": null, 265 | "notes": "" 266 | }, 267 | { 268 | "code": "2015", 269 | "parentCode": null, 270 | "level": "1", 271 | "name": "90 068 kroner", 272 | "shortName": "", 273 | "presentationName": "", 274 | "validFrom": null, 275 | "validTo": null, 276 | "notes": "" 277 | }, 278 | { 279 | "code": "2016", 280 | "parentCode": null, 281 | "level": "1", 282 | "name": "92 576 kroner", 283 | "shortName": "", 284 | "presentationName": "", 285 | "validFrom": null, 286 | "validTo": null, 287 | "notes": "" 288 | }, 289 | { 290 | "code": "2017", 291 | "parentCode": null, 292 | "level": "1", 293 | "name": "93 634 kroner", 294 | "shortName": "", 295 | "presentationName": "", 296 | "validFrom": null, 297 | "validTo": null, 298 | "notes": "" 299 | }, 300 | { 301 | "code": "2018", 302 | "parentCode": null, 303 | "level": "1", 304 | "name": "96 883 kroner", 305 | "shortName": "", 306 | "presentationName": "", 307 | "validFrom": null, 308 | "validTo": null, 309 | "notes": "" 310 | }, 311 | { 312 | "code": "2019", 313 | "parentCode": null, 314 | "level": "1", 315 | "name": "99 858 kroner", 316 | "shortName": "", 317 | "presentationName": "", 318 | "validFrom": null, 319 | "validTo": null, 320 | "notes": "" 321 | }, 322 | { 323 | "code": "2020", 324 | "parentCode": null, 325 | "level": "1", 326 | "name": "101 351 kroner", 327 | "shortName": "", 328 | "presentationName": "", 329 | "validFrom": null, 330 | "validTo": null, 331 | "notes": "" 332 | }, 333 | { 334 | "code": "2021", 335 | "parentCode": null, 336 | "level": "1", 337 | "name": "106 399 kroner", 338 | "shortName": "", 339 | "presentationName": "", 340 | "validFrom": null, 341 | "validTo": null, 342 | "notes": "" 343 | }, 344 | { 345 | "code": "2022", 346 | "parentCode": null, 347 | "level": "1", 348 | "name": "111 477 kroner", 349 | "shortName": "", 350 | "presentationName": "", 351 | "validFrom": null, 352 | "validTo": null, 353 | "notes": "" 354 | } 355 | ] 356 | } --------------------------------------------------------------------------------