Skip to content

Commit

Permalink
feat: upgrade to net8 (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
SondreJDigdir authored Dec 17, 2024
1 parent 3dfdd27 commit e70667a
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 84 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ local.settings.json
*.user
*.userosscache
*.sln.docstates
.idea

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
Expand Down
18 changes: 18 additions & 0 deletions src/Dan.Plugin.DATASOURCENAME/Config/PluginConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Dan.Plugin.DATASOURCENAME.Config;

public static class PluginConstants
{
// These are not mandatory, but there should be a distinct error code (any integer) for all types of errors that can occur. The error codes does not have to be globally
// unique. These should be used within either transient or permanent exceptions, see Plugin.cs for examples.
public const int ErrorUpstreamUnavailble = 1001;
public const int ErrorInvalidInput = 1002;
public const int ErrorNotFound = 1003;
public const int ErrorUnableToParseResponse = 1004;

// The datasets must supply a human-readable source description from which they originate. Individual fields might come from different sources, and this string should reflect that (ie. name all possible sources).
public const string SourceName = "Digitaliseringsdirektoratet";

// The function names (ie. HTTP endpoint names) and the dataset names must match. Using constants to avoid errors.
public const string SimpleDatasetName = "SimpleDataset";
public const string RichDatasetName = "RichDataset";
}
10 changes: 10 additions & 0 deletions src/Dan.Plugin.DATASOURCENAME/Config/Settings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Dan.Plugin.DATASOURCENAME.Config;

public class Settings
{
public int DefaultCircuitBreakerOpenCircuitTimeSeconds { get; init; }
public int DefaultCircuitBreakerFailureBeforeTripping { get; init; }
public int SafeHttpClientTimeout { get; init; }

public string EndpointUrl { get; init; }
}
11 changes: 6 additions & 5 deletions src/Dan.Plugin.DATASOURCENAME/Dan.Plugin.DATASOURCENAME.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dan.Common" Version="1.3.1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.9.0-preview1" OutputItemType="Analyzer" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
<PackageReference Include="Dan.Common" Version="1.6.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.0" OutputItemType="Analyzer" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="4.0.1" />
<PackageReference Include="NJsonSchema" Version="11.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
Expand Down
55 changes: 29 additions & 26 deletions src/Dan.Plugin.DATASOURCENAME/Metadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using Dan.Common.Enums;
using Dan.Common.Interfaces;
using Dan.Common.Models;
using Dan.Plugin.DATASOURCENAME.Config;
using Dan.Plugin.DATASOURCENAME.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Newtonsoft.Json.Schema.Generation;
using NJsonSchema;

namespace Dan.Plugin.DATASOURCENAME;

Expand All @@ -18,56 +20,57 @@ namespace Dan.Plugin.DATASOURCENAME;
public class Metadata : IEvidenceSourceMetadata
{
/// <summary>
///
///
/// </summary>
/// <returns></returns>
public List<EvidenceCode> GetEvidenceCodes()
{
JSchemaGenerator generator = new JSchemaGenerator();

return new List<EvidenceCode>()
{
new()
return
[
new EvidenceCode
{
EvidenceCodeName = global::Dan.Plugin.DATASOURCENAME.Plugin.SimpleDatasetName,
EvidenceSource = global::Dan.Plugin.DATASOURCENAME.Plugin.SourceName,
Values = new List<EvidenceValue>()
{
new()
EvidenceCodeName = PluginConstants.SimpleDatasetName,
EvidenceSource = PluginConstants.SourceName,
Values =
[
new EvidenceValue
{
EvidenceValueName = "field1",
ValueType = EvidenceValueType.String
},
new()

new EvidenceValue
{
EvidenceValueName = "field2",
ValueType = EvidenceValueType.String
}
}
]
},
new()
new EvidenceCode
{
EvidenceCodeName = global::Dan.Plugin.DATASOURCENAME.Plugin.RichDatasetName,
EvidenceSource = global::Dan.Plugin.DATASOURCENAME.Plugin.SourceName,
Values = new List<EvidenceValue>()
{
new()
EvidenceCodeName = PluginConstants.RichDatasetName,
EvidenceSource = PluginConstants.SourceName,
Values =
[
new EvidenceValue
{
// Convention for rich datasets with a single JSON model is to use the value name "default"
EvidenceValueName = "default",
ValueType = EvidenceValueType.JsonSchema,
JsonSchemaDefintion = generator.Generate(typeof(ExampleModel)).ToString()
JsonSchemaDefintion = JsonSchema
.FromType<ExampleModel>()
.ToJson(Newtonsoft.Json.Formatting.Indented)
}
},
AuthorizationRequirements = new List<Requirement>
{
],
AuthorizationRequirements =
[
new MaskinportenScopeRequirement
{
RequiredScopes = new List<string> { "altinn:dataaltinnno/somescope" }
RequiredScopes = ["altinn:dataaltinnno/somescope"]
}
}
]
}
};
];
}


Expand Down
2 changes: 2 additions & 0 deletions src/Dan.Plugin.DATASOURCENAME/Models/ExampleModel.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using Newtonsoft.Json;

namespace Dan.Plugin.DATASOURCENAME.Models;

[Serializable]
public class ExampleModel
{
[JsonRequired]
Expand Down
45 changes: 13 additions & 32 deletions src/Dan.Plugin.DATASOURCENAME/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Dan.Common.Interfaces;
using Dan.Common.Models;
using Dan.Common.Util;
using Dan.Plugin.DATASOURCENAME.Config;
using Dan.Plugin.DATASOURCENAME.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
Expand All @@ -24,20 +25,6 @@ public class Plugin
private readonly HttpClient _client;
private readonly Settings _settings;

// The datasets must supply a human-readable source description from which they originate. Individual fields might come from different sources, and this string should reflect that (ie. name all possible sources).
public const string SourceName = "Digitaliseringsdirektoratet";

// The function names (ie. HTTP endpoint names) and the dataset names must match. Using constants to avoid errors.
public const string SimpleDatasetName = "SimpleDataset";
public const string RichDatasetName = "RichDataset";

// These are not mandatory, but there should be a distinct error code (any integer) for all types of errors that can occur. The error codes does not have to be globally
// unique. These should be used within either transient or permanent exceptions, see Plugin.cs for examples.
private const int ERROR_UPSTREAM_UNAVAILBLE = 1001;
private const int ERROR_INVALID_INPUT = 1002;
private const int ERROR_NOT_FOUND = 1003;
private const int ERROR_UNABLE_TO_PARSE_RESPONSE = 1004;

public Plugin(
IHttpClientFactory httpClientFactory,
ILoggerFactory loggerFactory,
Expand All @@ -52,7 +39,7 @@ public Plugin(
_logger.LogDebug("Initialized plugin! This should be visible in the console");
}

[Function(SimpleDatasetName)]
[Function(PluginConstants.SimpleDatasetName)]
public async Task<HttpResponseData> GetSimpleDatasetAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequestData req,
FunctionContext context)
Expand All @@ -68,7 +55,7 @@ public async Task<HttpResponseData> GetSimpleDatasetAsync(
() => GetEvidenceValuesSimpledataset(evidenceHarvesterRequest));
}

[Function(RichDatasetName)]
[Function(PluginConstants.RichDatasetName)]
public async Task<HttpResponseData> GetRichDatasetAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequestData req,
FunctionContext context)
Expand All @@ -84,9 +71,9 @@ private async Task<List<EvidenceValue>> GetEvidenceValuesSimpledataset(EvidenceH
var url = _settings.EndpointUrl + "?someparameter=" + evidenceHarvesterRequest.OrganizationNumber;
var exampleModel = await MakeRequest<ExampleModel>(url);

var ecb = new EvidenceBuilder(_evidenceSourceMetadata, SimpleDatasetName);
ecb.AddEvidenceValue("field1", exampleModel.ResponseField1, SourceName);
ecb.AddEvidenceValue("field2", exampleModel.ResponseField2, SourceName);
var ecb = new EvidenceBuilder(_evidenceSourceMetadata, PluginConstants.SimpleDatasetName);
ecb.AddEvidenceValue("field1", exampleModel.ResponseField1, PluginConstants.SourceName);
ecb.AddEvidenceValue("field2", exampleModel.ResponseField2, PluginConstants.SourceName);

return ecb.GetEvidenceValues();
}
Expand All @@ -97,15 +84,9 @@ private async Task<List<EvidenceValue>> GetEvidenceValuesRichDataset(EvidenceHar
var url = _settings.EndpointUrl + "?someparameter=" + evidenceHarvesterRequest.OrganizationNumber;
var exampleModel = await MakeRequest<ExampleModel>(url);

var ecb = new EvidenceBuilder(_evidenceSourceMetadata, RichDatasetName);
var ecb = new EvidenceBuilder(_evidenceSourceMetadata, PluginConstants.RichDatasetName);

// Here we reserialize the model. While it is possible to merely send the received JSON string directly through without parsing it,
// the extra step of deserializing it to a known model ensures that the JSON schema supplied in the metadata always matches the
// dataset model.
//
// Another way to do this is to not generate the schema from the model, but "hand code" the schema in the metadata and validate the
// received JSON against it, throwing eg. a EvidenceSourcePermanentServerException if it fails to match.
ecb.AddEvidenceValue("default", JsonConvert.SerializeObject(exampleModel), SourceName);
ecb.AddEvidenceValue("default", exampleModel, PluginConstants.SourceName);

return ecb.GetEvidenceValues();
}
Expand All @@ -120,16 +101,16 @@ private async Task<T> MakeRequest<T>(string target)
}
catch (HttpRequestException ex)
{
throw new EvidenceSourceTransientException(ERROR_UPSTREAM_UNAVAILBLE, "Error communicating with upstream source", ex);
throw new EvidenceSourceTransientException(PluginConstants.ErrorUpstreamUnavailble, "Error communicating with upstream source", ex);
}

if (!result.IsSuccessStatusCode)
{
throw result.StatusCode switch
{
HttpStatusCode.NotFound => new EvidenceSourcePermanentClientException(ERROR_NOT_FOUND, "Upstream source could not find the requested entity (404)"),
HttpStatusCode.BadRequest => new EvidenceSourcePermanentClientException(ERROR_INVALID_INPUT, "Upstream source indicated an invalid request (400)"),
_ => new EvidenceSourceTransientException(ERROR_UPSTREAM_UNAVAILBLE, $"Upstream source retuned an HTTP error code ({(int)result.StatusCode})")
HttpStatusCode.NotFound => new EvidenceSourcePermanentClientException(PluginConstants.ErrorNotFound, "Upstream source could not find the requested entity (404)"),
HttpStatusCode.BadRequest => new EvidenceSourcePermanentClientException(PluginConstants.ErrorInvalidInput, "Upstream source indicated an invalid request (400)"),
_ => new EvidenceSourceTransientException(PluginConstants.ErrorUpstreamUnavailble, $"Upstream source retuned an HTTP error code ({(int)result.StatusCode})")
};
}

Expand All @@ -140,7 +121,7 @@ private async Task<T> MakeRequest<T>(string target)
catch (Exception ex)
{
_logger.LogError("Unable to parse data returned from upstream source: {exceptionType}: {exceptionMessage}", ex.GetType().Name, ex.Message);
throw new EvidenceSourcePermanentServerException(ERROR_UNABLE_TO_PARSE_RESPONSE, "Could not parse the data model returned from upstream source", ex);
throw new EvidenceSourcePermanentServerException(PluginConstants.ErrorUnableToParseResponse, "Could not parse the data model returned from upstream source", ex);
}
}
}
4 changes: 2 additions & 2 deletions src/Dan.Plugin.DATASOURCENAME/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Dan.Plugin.DATASOURCENAME;
using Microsoft.Extensions.Hosting;
using Dan.Common.Extensions;
using Dan.Plugin.DATASOURCENAME.Config;
using Microsoft.Extensions.DependencyInjection;

var host = new HostBuilder()
.ConfigureDanPluginDefaults()
.ConfigureAppConfiguration((context, configuration) =>
.ConfigureAppConfiguration((_, _) =>
{
// Add more configuration sources if necessary. ConfigureDanPluginDefaults will load environment variables, which includes
// local.settings.json (if developing locally) and applications settings for the Azure Function
Expand Down
10 changes: 0 additions & 10 deletions src/Dan.Plugin.DATASOURCENAME/Settings.cs

This file was deleted.

9 changes: 5 additions & 4 deletions src/Dan.Plugin.DATASOURCENAME/local.settings.json.template
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
{
"IsEncrypted": false,
"Host":{
"LocalHttpPort": 7075,
"CORS": "*",
"CORSCredentials": false
},
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",


"DefaultCircuitBreakerOpenCircuitTimeSeconds": "10",
"DefaultCircuitBreakerFailureBeforeTripping": "4",
"SafeHttpClientTimeout": "30",

"EndpointUrl": "https://example.com"

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.17.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down

0 comments on commit e70667a

Please sign in to comment.