diff --git a/.github/ISSUE_TEMPLATE/1-feature.yml b/.github/ISSUE_TEMPLATE/1-feature.yml index 3959b5f0..54b68ea7 100644 --- a/.github/ISSUE_TEMPLATE/1-feature.yml +++ b/.github/ISSUE_TEMPLATE/1-feature.yml @@ -17,6 +17,7 @@ body: - Authoring library - Compiler - Emulator library + - Documentation validations: required: true - type: input diff --git a/.github/ISSUE_TEMPLATE/2-bug.yml b/.github/ISSUE_TEMPLATE/2-bug.yml index 342c0d0d..5f11d634 100644 --- a/.github/ISSUE_TEMPLATE/2-bug.yml +++ b/.github/ISSUE_TEMPLATE/2-bug.yml @@ -42,6 +42,7 @@ body: - Authoring library - Compiler - Emulator library + - Documentation validations: required: true - type: input diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30fead8d..099341c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,10 @@ This will greatly increase alignment of your PR with the project goals and reduc For more information on how to set up the development environment, see [Development environment setup](docs/DevEnvironmentSetup.md). +### Implementation guides + + - [Add new policy guide](docs/AddPolicyGuide.md) + ## License This project welcomes contributions and suggestions. Most contributions require you to diff --git a/README.md b/README.md index 959f5a00..69e7efdb 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ The policy toolkit changes that. It allows you to write policy documents in C# l ## Documentation +:exclamation: Packages are only avaliable for download from github release. We are working to bring them to public nuget. + #### Azure API Management policy toolkit documentation for users. * [Quick start](docs/QuickStart.md) * [Available policies](docs/AvailablePolicies.md) diff --git a/docs/AddPolicyGuide.md b/docs/AddPolicyGuide.md new file mode 100644 index 00000000..9ab80e3a --- /dev/null +++ b/docs/AddPolicyGuide.md @@ -0,0 +1,150 @@ +# Contributing a new policy (feature) + +This guide outlines the steps to support a new policy, or new capabilities for an existing policy. + +All contributions should follow the steps provided in this guide to support a policy (feature) and its configuration. + +If you cannot follow the defined process, please open an issue to discuss before implementing it. + +## TL;DR + +When adding a new policy, you will typically need to create or modify the following files: + +- **Introduce the configuration**: `src/Authoring/Configs/YourPolicyConfig.cs` (Exampe: `RateLimitConfig.cs`) +- **Enable using in respective section or fragment**: `src/Authoring/IInboundContext.cs` (or other context) +- **Support compilation**: `src/Core/Compiling/Policy/YourPolicyCompiler.cs` (Exampe: `RateLimitCompiler.cs`) +- **Provide automated tests**: `test/Test.Core/Compiling/YourPolicyTests.cs` (Exampe: `RateLimitTests.cs`) +- **Document your policy**: `docs/AvailablePolicies.md` + +We recommend using existing policies as detailed examples, such as `RateLimit` or `Quota` policies. These contain all possible aspects of a policy compilation implementation. + +## Steps to add a new policy + +- Create `src/Authoring/Configs/YourPolicyConfig.cs` as a public record. + - Required parameters as `required` `init` properties + - Optional properties should be `nullable` + - Properties allowing policy expressions should have `[ExpressionAllowed]` attribute assigned. + - Add XML documentation for the record and its properties. + +```csharp + /// + /// Description of config. + /// + public record YourPolicyConfig + { + /// + /// Description of your property. + /// + [ExpressionAllowed] + public required string Property { get; init; } + + /// + /// Optional property description. + /// + [ExpressionAllowed] + public int? OptionalProperty { get; init; } + + // Add more properties as needed. + } +``` + +- Add a method signature to section context interfaces in which policy is avaliable (e.g. `src/Authoring/IInboundContext.cs`). + Add method to policy fragment context interface to make policy avaliable in policy fragment. + Make sure to add XML documentation. + +```csharp + /// + /// Description of your policy. + /// + /// Configuration for the YourPolicy policy. + /// + /// + void YourPolicy(YourPolicyConfig config); +``` + +- Create compiler class `src/Core/Compiling/Policy/YourPolicyCompiler.cs` implementing `IMethodPolicyHandler`. + This class will be responsible for translating the C# method call into the corresponding XML policy element. + - Make sure that the class is `public` and in `Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling.Policy` namespace. + This is required for the automatic adding your policy compiler to the compilation. + - Implement the `Handle` method to extract parameters from the method invocation and construct the XML element. + - Use `TryExtractingConfigParameter` to extract the config object into initialization object. + - Use `AddAttribute` extension method to add attributes to the XML element. + - Report diagnostics for missing required parameters using `context.ReportDiagnostic`. + For avaliable errors see `CompilationErrors.cs` file. + - For complex policies with sub elements, refer to existing compilers for guidance (eg. RateLimitCompiler). + +```csharp +namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling.Policy; + +public class YourPolicyCompiler : IMethodPolicyHandler +{ + public string MethodName => nameof(IInboundContext.YourPolicy); + + public void Handle(IDocumentCompilationContext context, InvocationExpressionSyntax node) + { + if (!node.TryExtractingConfigParameter(context, "your-policy", out var values)) + { + return; + } + + var element = new XElement("your-policy"); + + if (!element.AddAttribute(values, nameof(YourPolicyConfig.Property), "propery")) + { + context.Report(Diagnostic.Create( + CompilationErrors.RequiredParameterNotDefined, + node.GetLocation(), + "your-policy", + nameof(YourPolicyConfig.Property) + )); + return; + } + + element.AddAttribute(values, nameof(YourPolicyConfig.OptionalProperty), "optional-property"); + + context.AddPolicy(element); + } +} +``` + +- Add tests to `test/Test.Core/Compiling/YourPolicyTests.cs`. + - Use a `[DataRow]` for each test case, following the pattern of existing tests. + - Check that compiler + - handles compiling policy in all of sections which you added the method to + - required aparameters with constant values + - optional parameters with constant values + - expressions for properties which define [ExpressionAllowed] attribute + +```csharp +[TestClass] +public class YourPolicyTests : PolicyCompilerTestBase +{ + [TestMethod] + [DataRow( + """ + [Document] + public class PolicyDocument : IDocument + { + public void Inbound(IInboundContext context) { + context.YourPolicy(new YourPolicyConfig() + { + Property = "Value" + }); + } + } + """, + """ + + + + + + """)] + public void ShouldCompileYourPolicy(string code, string expectedXml) + { + code.CompileDocument().Should().BeSuccessful().And.DocumentEquivalentTo(expectedXml); + } +} +``` + +- Update `docs/AvailablePolicies.md` to include your new policy in the list of implemented policies. diff --git a/docs/AvailablePolicies.md b/docs/AvailablePolicies.md index f9139998..099f3ec6 100644 --- a/docs/AvailablePolicies.md +++ b/docs/AvailablePolicies.md @@ -1,57 +1,103 @@ # Available Policies -The Project is in the development stage. -That means that not all policies are implemented yet. -In this document, you can find a list of implemented policies. For policy details, see the Azure API Management [policy reference](https://learn.microsoft.com/azure/api-management/api-management-policies). - -#### :white_check_mark: Implemented policies - -* authentication-basic -* authentication-certificate -* authentication-managed-identity -* azure-openai-emit-token-metric -* azure-openai-semantic-cache-lookup -* azure-openai-semantic-cache-store -* base -* cache-lookup -* cache-lookup-value -* cache-remove-value -* cache-store -* cache-store-value -* check-header -* choose -* cors -* emit-metric -* find-and-replace -* forward-request -* ip-filter -* json-to-xml -* jsonp -* llm-emit-token-metric -* llm-semantic-cache-lookup -* llm-semantic-cache-store -* mock-response -* quota -* rate-limit -* rate-limit-by-key -* return-response -* rewrite-uri -* send-request -* set-backend-service -* set-body -* set-header -* set-method -* set-query-parameter -* set-variable -* validate-jwt - -Policies not listed here are not implemented yet, we are curious to know which [ones you'd like to use and are happy to review contributions](./../CONTRIBUTING.md). - -## InlinePolicy - -InlinePolicy is a workaround until all the policies are implemented. +This document lists policy elements that the toolkit's authoring and compiler support by mapping C# APIs to Azure API +Management policy elements. The list below was generated from the public authoring interfaces ( +inbound/outbound/backend/on-error/fragment contexts) and reflects which policies the toolkit can compile into XML. + +If a policy you need is missing you can either: + +- Use `InlinePolicy(...)` to insert raw XML into the compiled document, or +- Open an issue / contribute the feature (learn more in [our contribution guide](../CONTRIBUTING.md)). + +Notes: + +- The C# compiler maps C# constructs to policy constructs. For example, if/else in C# is compiled to the `choose` + policy (with `when` / `otherwise`). +- `InlinePolicy(string)` allows inserting arbitrary XML when a policy isn't implemented as a first-class API. + +## Implemented policies + +- authentication-basic +- authentication-certificate +- authentication-managed-identity +- azure-openai-emit-token-metric +- azure-openai-semantic-cache-lookup +- azure-openai-semantic-cache-store +- azure-openai-token-limit +- base +- cache-lookup +- cache-lookup-value +- cache-remove-value +- cache-store +- cache-store-value +- check-header +- choose (implemented via C# if/else -> choose/when/otherwise) +- cors +- cross-domain +- emit-metric +- find-and-replace +- forward-request +- get-authorization-context +- include-fragment +- inline-policy (method to insert raw XML) +- invoke-dapr-binding (publish/send to Dapr bindings) +- ip-filter +- json-to-xml +- jsonp +- limit-concurrency +- llm-content-safety +- llm-emit-token-metric +- llm-semantic-cache-lookup +- llm-semantic-cache-store +- llm-token-limit +- log-to-eventhub +- mock-response +- proxy +- publish-to-dapr +- quota +- quota-by-key +- rate-limit +- rate-limit-by-key +- redirect-content-urls +- remove-header (via SetHeader/RemoveHeader APIs) +- remove-query-parameter (via SetQueryParameter/Remove APIs) +- return-response +- retry +- rewrite-uri +- send-one-way-request +- send-request +- set-backend-service +- set-body +- set-header +- set-header-if-not-exist +- set-method +- set-query-parameter +- set-query-parameter-if-not-exist +- set-status +- set-variable +- trace +- validate-azure-ad-token +- validate-client-certificate +- validate-content +- validate-headers +- validate-jwt +- validate-odata-request +- validate-parameters +- validate-status-code +- wait +- xml-to-json +- xsl-transform + +## How to work around missing policies + +InlinePolicy is a workaround until all the policies are implemented or new policies are not added yet to toolkit. It allows you to include policy not implemented yet to the document. ```csharp c.InlinePolicy(""); ``` + +## Contributing + +If you'd like a specific policy implemented natively in the toolkit, please open an issue or a pull request in this +repository. See [CONTRIBUTING.md](../CONTRIBUTING.md) and [Add new policy guide](AddPolicyGuide.md) for guidance. diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 55bc45d5..56ad5a51 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -36,17 +36,19 @@ We will cover the following topics: + + ``` -5. Add Azure API Management policy toolkit library by running +5. Add Azure API Management policy authoring toolkit library by running ```shell cd ./Contoso.Apis.Policies dotnet add package Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring ``` 6. Open the solution in your IDE of choice. We - tested [Visual Studio ](https://visualstudio.microsoft.com), [Raider](https://www.jetbrains.com/rider/), [Visual Studio Code](https://code.visualstudio.com/) + tested [Visual Studio ](https://visualstudio.microsoft.com), [Rider](https://www.jetbrains.com/rider/), [Visual Studio Code](https://code.visualstudio.com/) with [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit), but any IDE with C# support should work. @@ -62,10 +64,10 @@ dotnet new class -n ApiOperationPolicy ``` The class in the file should inherit from `IDocument` interface and have `Document` attribute -from `Azure.ApiManagement.PolicyToolkit.Authoring` namespace. +from `Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring` namespace. ```csharp -using Azure.ApiManagement.PolicyToolkit.Authoring; +using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring; namespace Contoso.Apis.Policies; @@ -121,14 +123,14 @@ cd .. # Go to solution folder if not already there dotnet tool install Azure.ApiManagement.PolicyToolkit.Compiling ```` -After the installation, the compiler should be available in the project folder. -Now lets run the compiler to generate policy document. Execute compiler command in the solution folder. +After the installation, the compiler should be available in the project folder as the `azure-apim-policy-compiler` command. +Now let's run the compiler to generate policy document. Execute compiler command in the solution folder. ```shell dotnet azure-apim-policy-compiler --s .\Contoso.Apis.Policies --o . --format true -``` +``` -The compiler is a dotnet tool named `azure-apim-policy-compiler`. The `--s` parameter is a source folder with policy documents. +The compiler is a dotnet tool whose command name is `azure-apim-policy-compiler`. The `--s` parameter is a source folder with policy documents. The `--o` parameter is an output folder for generated policy documents. The `--format` parameter is a flag which tells the compiler to format the generated document. @@ -169,8 +171,8 @@ If a request comes from other IP addresses it should use `Bearer` token received For every request we want to add header with the user id. ```csharp -using Azure.ApiManagement.PolicyToolkit.Authoring; -using Azure.ApiManagement.PolicyToolkit.Authoring.Expressions; +using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring; +using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring.Expressions; namespace Contoso.Apis.Policies; @@ -209,7 +211,7 @@ Let's unpack the code above it: expression * Every method, other than section method are treated as expressions. They need to accept one parameter of type `IExpressionContext` - with name `context`. Type is available in `Azure.ApiManagement.PolicyToolkit.Authoring.Expressions` namespace. + with name `context`. Type is available in `Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring.Expressions` namespace. * `IExpressionContext` type contains the same properties as `context` object in policy expressions. * `AuthenticationBasic` method is mapped to `authentication-basic` policy. * `AuthenticationManagedIdentity` method is mapped to `authentication-managed-identity` policy. @@ -269,7 +271,7 @@ the following commands. dotnet new mstest --output Contoso.Apis.Policies.Tests dotnet sln add ./Contoso.Apis.Policies.Tests cd Contoso.Apis.Policies.Tests -dotnet add package Azure.ApiManagement.PolicyToolkit.Testing +dotnet add package Microsoft.Azure.ApiManagement.PolicyToolkit.Testing dotnet add reference ..\Contoso.Apis.Policies dotnet new class -n ApiOperationPolicyTest ``` @@ -279,7 +281,7 @@ Perfect! Now we can write a test for `IsCompanyIP` method in the class. ```csharp using Contoso.Apis.Policies; -using Azure.ApiManagement.PolicyToolkit.Testing.Expressions; +using Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Expressions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; @@ -307,7 +309,7 @@ Let's unpack the code above: * Test class is a standard MSTest class with one test method. You can use your favorite testing framework in place of MSTest. Policy framework is not dependent on any testing framework. * `MockExpressionContext` is a class which is used to mock request context. It is available in - `Azure.ApiManagement.PolicyToolkit.Testing.Expressions` namespace. It implements `IExpressionContext` + `Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Expressions` namespace. It implements `IExpressionContext` interface and exposes helper properties to set up request context. * `context.MockRequest.IpAddress = "10.0.0.12"` is setting a IpAddress for request. diff --git a/src/Authoring/README.md b/src/Authoring/README.md index 733a74d2..a605b119 100644 --- a/src/Authoring/README.md +++ b/src/Authoring/README.md @@ -20,7 +20,8 @@ dotnet add package Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring ### Write your first policy ```csharp -using Azure.ApiManagement.PolicyToolkit.Authoring; +using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring; +using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring.Expressions; namespace Contoso.Apis.Policies; diff --git a/src/Compiling/README.md b/src/Compiling/README.md index 9d540082..4fec576d 100644 --- a/src/Compiling/README.md +++ b/src/Compiling/README.md @@ -1,27 +1,15 @@ -# Microsoft Azure ApiManagement Policy Toolkit policy compiler tool +# Azure API Management Policy Toolkit Compiler -Microsoft Azure API Management is a hybrid, multicloud management platform for APIs across all environments. As a -platform-as-a-service, API Management supports the complete API lifecycle. - -This library contains .net tool which transforms policies wrote in C# to format accepted by Microsoft Azure Api -Management. - -## Getting started - -### Install compiler CLI tool +This project builds a dotnet tool which can compile C# policy documents into Azure API Management XML (rawxml / Razor) +policy documents. +## Install Install the Microsoft Azure Api Management Policy Toolkit compiler CLI tool with [NuGet][nuget]: -```dotnetcli +```shell dotnet tool install Azure.ApiManagement.PolicyToolkit.Compiling ``` -### Compile the policy - -```bash -dotnet azure-apim-policy-compiler --s .\PATH\TO\SOURCE\FOLDER --o .\PATH\TO\OUTPUT\FOLDER -``` - ### Inspect generated policy ```cshtml diff --git a/src/Testing/README.md b/src/Testing/README.md index 80a969b6..125f486f 100644 --- a/src/Testing/README.md +++ b/src/Testing/README.md @@ -13,14 +13,14 @@ expression and policy documents wrote in C# for Microsoft Azure Api Management. Install the Microsoft Azure Api Management Policy Toolkit test library with [NuGet][nuget]: ```dotnetcli -dotnet tool install Azure.ApiManagement.PolicyToolkit.Testing +dotnet add package Microsoft.Azure.ApiManagement.PolicyToolkit.Testing ``` ### Write test ```cs using Contoso.Apis.Policies; -using Azure.ApiManagement.PolicyToolkit.Testing.Expressions; +using Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Expressions; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Contoso.Apis.Policies.Tests; @@ -31,6 +31,7 @@ public class ApiOperationPolicyTest [TestMethod] public void TestHelloFromExpression() { + var context = new MockExpressionContext(); Assert.AreEqual("World", ApiOperationPolicy.HelloFromExpression(context)); } }