├── .editorconfig
├── .github
├── CODEOWNERS
├── component_owners.yml
└── workflows
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ ├── component-owners.yml
│ ├── dco-merge-group.yml
│ ├── dotnet-format.yml
│ ├── lint-pr.yml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── .release-please-manifest.json
├── .vscode
└── settings.json
├── CONTRIBUTING.md
├── DotnetSdkContrib.slnx
├── LICENSE
├── README.md
├── build
├── Common.prod.props
├── Common.props
├── Common.tests.props
├── openfeature-icon.png
└── xunit.runner.json
├── global.json
├── nuget.config
├── release-please-config.json
├── renovate.json
├── src
├── Directory.Build.props
├── Directory.Build.targets
├── OpenFeature.Contrib.Hooks.Otel
│ ├── CHANGELOG.md
│ ├── MetricsConstants.cs
│ ├── MetricsHook.cs
│ ├── OpenFeature.Contrib.Hooks.Otel.csproj
│ ├── OtelHook.cs
│ ├── README.md
│ ├── TracingHook.cs
│ ├── assets
│ │ ├── otlp-error.png
│ │ └── otlp-success.png
│ └── version.txt
├── OpenFeature.Contrib.Providers.ConfigCat
│ ├── CHANGELOG.md
│ ├── ConfigCatProvider.cs
│ ├── OpenFeature.Contrib.Providers.ConfigCat.csproj
│ ├── README.md
│ └── UserBuilder.cs
├── OpenFeature.Contrib.Providers.EnvVar
│ ├── CHANGELOG.md
│ ├── EnvVarProvider.cs
│ ├── OpenFeature.Contrib.Providers.EnvVar.csproj
│ ├── README.md
│ └── version.txt
├── OpenFeature.Contrib.Providers.FeatureManagement
│ ├── CHANGELOG.md
│ ├── FeatureManagementProvider.cs
│ ├── OpenFeature.Contrib.Providers.FeatureManagement.csproj
│ ├── README.md
│ └── version.txt
├── OpenFeature.Contrib.Providers.Flagd
│ ├── CHANGELOG.md
│ ├── Cache.cs
│ ├── DependencyInjection
│ │ ├── FeatureBuilderExtensions.cs
│ │ ├── FlagdProviderOptions.cs
│ │ └── FlagdProviderOptionsExtensions.cs
│ ├── FlagdConfig.cs
│ ├── FlagdProvider.cs
│ ├── OpenFeature.Contrib.Providers.Flagd.csproj
│ ├── README.md
│ ├── Resolver
│ │ ├── InProcess
│ │ │ ├── CustomEvaluators
│ │ │ │ ├── FlagdProperties.cs
│ │ │ │ ├── FractionalRule.cs
│ │ │ │ ├── SemVerRule.cs
│ │ │ │ └── StringRule.cs
│ │ │ ├── InProcessResolver.cs
│ │ │ ├── JsonEvaluator.cs
│ │ │ └── JsonSchemaValidator.cs
│ │ ├── Resolver.cs
│ │ └── Rpc
│ │ │ └── RpcResolver.cs
│ ├── UnixDomainSocketConnectionFactory.cs
│ ├── Utils
│ │ └── CertificateLoader.cs
│ └── version.txt
├── OpenFeature.Contrib.Providers.Flagsmith
│ ├── CHANGELOG.md
│ ├── FlagsmithProvider.cs
│ ├── FlagsmithProviderConfiguration.cs
│ ├── IFlagsmithProviderConfiguration.cs
│ ├── OpenFeature.Contrib.Providers.Flagsmith.csproj
│ ├── README.md
│ └── version.txt
├── OpenFeature.Contrib.Providers.Flipt
│ ├── CHANGELOG.md
│ ├── ClientWrapper
│ │ ├── FliptClientWrapper.cs
│ │ └── IFliptClientWrapper.cs
│ ├── Converters
│ │ ├── JsonConverterExtensions.cs
│ │ ├── OpenFeatureStructureConverter.cs
│ │ └── OpenFeatureValueConverter.cs
│ ├── FliptExtensions.cs
│ ├── FliptProvider.cs
│ ├── FliptToOpenFeatureConverter.cs
│ ├── OpenFeature.Contrib.Providers.Flipt.csproj
│ ├── README.md
│ ├── openapi.yaml
│ └── version.txt
├── OpenFeature.Contrib.Providers.GOFeatureFlag
│ ├── CHANGELOG.md
│ ├── GoFeatureFlagProvider.cs
│ ├── GoFeatureFlagProviderOptions.cs
│ ├── OpenFeature.Contrib.Providers.GOFeatureFlag.csproj
│ ├── README.md
│ ├── converters
│ │ ├── DictionaryConverter.cs
│ │ ├── JsonConverterExtensions.cs
│ │ ├── OpenFeatureStructureConverter.cs
│ │ └── OpenFeatureValueConverter.cs
│ ├── exception
│ │ ├── FlagDisabled.cs
│ │ ├── FlagNotFoundError.cs
│ │ ├── GeneralError.cs
│ │ ├── GoFeatureFlagException.cs
│ │ ├── ImpossibleToConvertTypeError.cs
│ │ ├── InvalidEvaluationContext.cs
│ │ ├── InvalidOption.cs
│ │ ├── InvalidTargetingKey.cs
│ │ ├── TypeMismatchError.cs
│ │ └── UnauthorizedError.cs
│ ├── extensions
│ │ └── GoFeatureFlagExtensions.cs
│ ├── hooks
│ │ └── EnrichEvaluationContextHook.cs
│ ├── models
│ │ ├── ExporterMetadata.cs
│ │ ├── OfrepRequest.cs
│ │ └── OfrepResponse.cs
│ └── version.txt
└── OpenFeature.Contrib.Providers.Statsig
│ ├── CHANGELOG.md
│ ├── EvaluationContextExtensions.cs
│ ├── OpenFeature.Contrib.Providers.Statsig.csproj
│ ├── README.md
│ ├── StatsigProvider.cs
│ └── version.txt
└── test
├── Directory.Build.props
├── OpenFeature.Contrib.Hooks.Otel.Test
├── MetricsHookTest.cs
├── OpenFeature.Contrib.Hooks.Otel.Test.csproj
└── TracingHookTest.cs
├── OpenFeature.Contrib.Providers.ConfigCat.Test
├── ConfigCatProviderTest.cs
├── OpenFeature.Contrib.Providers.ConfigCat.Test.csproj
└── UserBuilderTests.cs
├── OpenFeature.Contrib.Providers.EnvVar.Test
├── EnvVarProviderTests.cs
└── OpenFeature.Contrib.Providers.EnvVar.Test.csproj
├── OpenFeature.Contrib.Providers.FeatureManagement.Test
├── FeatureManagementProviderSimpleFlagTest.cs
├── FeatureManagementProviderTestNoContext.cs
├── FeatureManagementProviderTestWithContext.cs
├── OpenFeature.Contrib.Providers.FeatureManagement.Test.csproj
├── TestData.cs
├── appsettings.enabled.json
└── appsettings.targeting.json
├── OpenFeature.Contrib.Providers.Flagd.E2e.Common
├── OpenFeature.Contrib.Providers.Flagd.E2e.Common.csproj
└── Steps
│ ├── EvaluationStepDefinitionBase.cs
│ └── FlagdStepDefinitionBase.cs
├── OpenFeature.Contrib.Providers.Flagd.E2e.ProcessTest
├── Features
│ ├── .gitignore
│ └── .gitkeep
├── FlagdSyncTestBedContainer.cs
├── OpenFeature.Contrib.Providers.Flagd.E2e.ProcessTest.csproj
└── Steps
│ ├── EvaluationStepDefinitionsProcess.cs
│ ├── FlagdStepDefinitionsProcess.cs
│ └── TestHooks.cs
├── OpenFeature.Contrib.Providers.Flagd.E2e.RpcTest
├── Features
│ ├── .gitignore
│ └── .gitkeep
├── FlagdRpcTestBedContainer.cs
├── OpenFeature.Contrib.Providers.Flagd.E2e.RpcTest.csproj
└── Steps
│ ├── EvaluationStepDefinitionsRpc.cs
│ ├── FlagdStepDefinitionsRpc.cs
│ └── TestHooks.cs
├── OpenFeature.Contrib.Providers.Flagd.Test
├── CacheTest.cs
├── FeatureBuilderExtensionsTests.cs
├── FlagdConfigTest.cs
├── FlagdProviderOptionsExtensionsTests.cs
├── FlagdProviderTest.cs
├── FractionalEvaluatorTest.cs
├── JsonEvaluatorTest.cs
├── JsonSchemaValidatorTests.cs
├── OpenFeature.Contrib.Providers.Flagd.Test.csproj
├── SemVerEvaluatorTest.cs
├── StringEvaluatorTest.cs
├── Utils.cs
└── mycert.pem
├── OpenFeature.Contrib.Providers.Flagsmith.Test
├── FlagsmithProviderTest.cs
└── OpenFeature.Contrib.Providers.Flagsmith.Test.csproj
├── OpenFeature.Contrib.Providers.Flipt.Test
├── FliptExtensionsTest.cs
├── FliptProviderTest.cs
├── FliptToOpenFeatureConverterTest.cs
└── OpenFeature.Contrib.Providers.Flipt.Test.csproj
├── OpenFeature.Contrib.Providers.GOFeatureFlag.Test
├── GoFeatureFlagProviderTest.cs
├── OfrepSerializationTest.cs
└── OpenFeature.Contrib.Providers.GOFeatureFlag.Test.csproj
└── OpenFeature.Contrib.Providers.Statsig.Test
├── EvaluationContextExtensionsTests.cs
├── OpenFeature.Contrib.Providers.Statsig.Test.csproj
└── StatsigProviderTest.cs
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # These owners will be the default owners for everything in
2 | # the repo. Unless a later match takes precedence
3 | #
4 | # Managed by Peribolos: https://github.com/open-feature/community/blob/main/config/open-feature/sdk-dotnet/workgroup.yaml
5 | #
6 | * @open-feature/sdk-dotnet-maintainers @open-feature/sdk-dotnet-approvers
7 |
--------------------------------------------------------------------------------
/.github/component_owners.yml:
--------------------------------------------------------------------------------
1 | # Keep all in alphabetical order
2 | components:
3 | # src/
4 | src/OpenFeature.Contrib.Hooks.Otel:
5 | - bacherfl
6 | - toddbaert
7 | - askpt
8 | src/OpenFeature.Contrib.Providers.Flagd:
9 | - bacherfl
10 | - toddbaert
11 | src/OpenFeature.Contrib.Providers.GOFeatureFlag:
12 | - thomaspoignant
13 | src/OpenFeature.Contrib.Providers.Flagsmith:
14 | - vpetrusevici
15 | - matthewelwell
16 | src/OpenFeature.Contrib.Providers.ConfigCat:
17 | - luizbon
18 | - adams85
19 | src/OpenFeature.Contrib.Providers.FeatureManagement:
20 | - ericpattison
21 | - toddbaert
22 | src/OpenFeature.Contrib.Providers.Statsig:
23 | - jenshenneberg
24 | - lattenborough
25 | src/OpenFeature.Contrib.Providers.Flipt:
26 | - jeandreidc
27 | - markphelps
28 |
29 | # test/
30 | test/OpenFeature.Contrib.Hooks.Otel.Test:
31 | - bacherfl
32 | - toddbaert
33 | - askpt
34 | test/OpenFeature.Contrib.Providers.Flagd.Test:
35 | - bacherfl
36 | - toddbaert
37 | test/OpenFeature.Contrib.Providers.GOFeatureFlag.Test:
38 | - thomaspoignant
39 | test/OpenFeature.Contrib.Providers.Flagsmith.Test:
40 | - vpetrusevici
41 | - matthewelwell
42 | test/OpenFeature.Contrib.Providers.ConfigCat.Test:
43 | - luizbon
44 | - adams85
45 | test/OpenFeature.Contrib.Providers.FeatureManagement.Test:
46 | - ericpattison
47 | - toddbaert
48 | test/src/OpenFeature.Contrib.Providers.Statsig.Test:
49 | - jenshenneberg
50 | - lattenborough
51 | test/src/OpenFeature.Contrib.Providers.Flipt.Test:
52 | - jeandreidc
53 | - markphelps
54 |
55 | ignored-authors:
56 | - renovate-bot
57 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - name
7 | paths-ignore:
8 | - "**.md"
9 | pull_request:
10 | types:
11 | - opened
12 | - synchronize
13 | - reopened
14 | branches:
15 | - main
16 | merge_group:
17 |
18 | jobs:
19 | build:
20 | strategy:
21 | matrix:
22 | os: [ubuntu-latest, windows-latest]
23 |
24 | runs-on: ${{ matrix.os }}
25 |
26 | permissions:
27 | contents: read
28 | pull-requests: write
29 | packages: read
30 |
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
34 | with:
35 | fetch-depth: 0
36 | submodules: recursive
37 |
38 | - name: Setup .NET SDK
39 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
40 | env:
41 | NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42 | with:
43 | global-json-file: global.json
44 | source-url: https://nuget.pkg.github.com/open-feature/index.json
45 |
46 | - name: Restore
47 | run: dotnet restore
48 |
49 | - name: Build
50 | run: dotnet build --no-restore
51 |
52 | - name: Test
53 | run: dotnet test --no-build --logger GitHubActions
54 |
55 | e2e:
56 | runs-on: ubuntu-latest
57 | permissions:
58 | contents: read
59 | pull-requests: write
60 | packages: read
61 | steps:
62 | - name: Checkout
63 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
64 | with:
65 | fetch-depth: 0
66 | submodules: recursive
67 |
68 | - name: Setup .NET SDK
69 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
70 | env:
71 | NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 | with:
73 | global-json-file: global.json
74 | source-url: https://nuget.pkg.github.com/open-feature/index.json
75 |
76 | - name: Test
77 | run: dotnet build && E2E=true dotnet test --logger GitHubActions
78 |
79 | packaging:
80 | needs: build
81 |
82 | permissions:
83 | contents: read
84 | packages: write
85 |
86 | runs-on: ubuntu-latest
87 |
88 | steps:
89 | - name: Checkout
90 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
91 | with:
92 | fetch-depth: 0
93 | submodules: recursive
94 |
95 | - name: Setup .NET SDK
96 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
97 | env:
98 | NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
99 | with:
100 | global-json-file: global.json
101 | source-url: https://nuget.pkg.github.com/open-feature/index.json
102 |
103 | - name: Restore
104 | run: dotnet restore
105 |
106 | - name: Pack NuGet packages (CI versions)
107 | if: startsWith(github.ref, 'refs/heads/')
108 | run: dotnet pack --no-restore --version-suffix "ci.$(date -u +%Y%m%dT%H%M%S)+sha.${GITHUB_SHA:0:9}"
109 |
110 | - name: Pack NuGet packages (PR versions)
111 | if: startsWith(github.ref, 'refs/pull/')
112 | run: dotnet pack --no-restore --version-suffix "pr.$(date -u +%Y%m%dT%H%M%S)+sha.${GITHUB_SHA:0:9}"
113 |
114 | - name: Publish NuGet packages (base)
115 | if: github.event.pull_request.head.repo.fork == false
116 | run: dotnet nuget push "src/**/*.nupkg" --api-key "${{ secrets.GITHUB_TOKEN }}" --source https://nuget.pkg.github.com/open-feature/index.json --skip-duplicate
117 |
118 | - name: Publish NuGet packages (fork)
119 | if: github.event.pull_request.head.repo.fork == true
120 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
121 | with:
122 | name: nupkgs
123 | path: src/**/*.nupkg
124 |
--------------------------------------------------------------------------------
/.github/workflows/component-owners.yml:
--------------------------------------------------------------------------------
1 | name: 'Component Owners'
2 | on:
3 | pull_request_target:
4 |
5 | permissions:
6 | contents: read # to read changed files
7 | issues: write # to read/write issue assignees
8 | pull-requests: write # to read/write PR reviewers
9 |
10 | jobs:
11 | run_self:
12 | runs-on: ubuntu-latest
13 | name: Auto Assign Owners
14 | steps:
15 | - uses: dyladan/component-owners@58bd86e9814d23f1525d0a970682cead459fa783
16 | with:
17 | config-file: .github/component_owners.yml
18 | repo-token: ${{ secrets.GITHUB_TOKEN }}
19 |
--------------------------------------------------------------------------------
/.github/workflows/dco-merge-group.yml:
--------------------------------------------------------------------------------
1 | name: DCO
2 | on:
3 | merge_group:
4 |
5 | # Workaround because the DCO app doesn't run on a merge_group trigger
6 | # https://github.com/dcoapp/app/pull/200
7 | jobs:
8 | DCO:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | contents: read
12 | pull-requests: write
13 | if: ${{ github.actor != 'renovate[bot]' }}
14 | steps:
15 | - run: echo "dummy DCO workflow (it won't run any check actually) to trigger by merge_group in order to enable merge queue"
16 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-format.yml:
--------------------------------------------------------------------------------
1 | name: dotnet format
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | check-format:
11 | runs-on: ubuntu-latest
12 | permissions:
13 | contents: read
14 | pull-requests: write
15 | packages: read
16 |
17 | steps:
18 | - name: Check out code
19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
20 | with:
21 | fetch-depth: 0
22 | submodules: recursive
23 |
24 | - name: Setup .NET
25 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
26 | env:
27 | NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 | with:
29 | global-json-file: global.json
30 | source-url: https://nuget.pkg.github.com/open-feature/index.json
31 |
32 | - name: Restore
33 | run: dotnet restore
34 |
35 | - name: Build
36 | run: dotnet build --no-restore
37 |
38 | - name: dotnet format
39 | run: dotnet format --verify-no-changes --no-restore DotnetSdkContrib.slnx
40 |
--------------------------------------------------------------------------------
/.github/workflows/lint-pr.yml:
--------------------------------------------------------------------------------
1 | name: "Lint PR"
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | jobs:
11 | main:
12 | name: Validate PR title
13 | permissions:
14 | contents: read
15 | pull-requests: write
16 | runs-on: ubuntu-latest
17 | steps:
18 | - id: lint_pr_title
19 | uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 |
23 | - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2
24 | # When the previous steps fails, the workflow would stop. By adding this
25 | # condition you can continue the execution with the populated error message.
26 | if: always() && (steps.lint_pr_title.outputs.error_message != null)
27 | with:
28 | header: pr-title-lint-error
29 | message: |
30 | Hey there and thank you for opening this pull request! 👋🏼
31 |
32 | We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
33 | Details:
34 |
35 | ```
36 | ${{ steps.lint_pr_title.outputs.error_message }}
37 | ```
38 | # Delete a previous comment when the issue has been resolved
39 | - if: ${{ steps.lint_pr_title.outputs.error_message == null }}
40 | uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2
41 | with:
42 | header: pr-title-lint-error
43 | delete: true
44 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Run Release Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | release-package:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write # for googleapis/release-please-action to create release commit
13 | pull-requests: write # for googleapis/release-please-action to create release PR
14 | issues: write # for googleapis/release-please-action to create labels
15 |
16 | steps:
17 | - uses: googleapis/release-please-action@a02a34c4d625f9be7cb89156071d8567266a2445 #v4
18 | id: release
19 | with:
20 | token: ${{secrets.RELEASE_PLEASE_ACTION_TOKEN}}
21 | outputs:
22 | all: ${{ toJSON(steps.release.outputs) }}
23 | releases_created: ${{ steps.release.outputs.releases_created }}
24 | paths_released: ${{ steps.release.outputs.paths_released }}
25 |
26 | release:
27 | needs: release-package
28 | environment: publish
29 | runs-on: ubuntu-latest
30 | permissions:
31 | id-token: write
32 | contents: write # upload sbom to a release
33 | attestations: write
34 | packages: read # for internal nuget reading
35 | if: ${{ fromJSON(needs.release-package.outputs.releases_created || false) }}
36 | strategy:
37 | matrix:
38 | release: ${{ fromJSON(needs.release-package.outputs.paths_released) }}
39 | env:
40 | TAG_NAME: ${{ fromJSON(needs.release-package.outputs.all)[format('{0}--tag_name', matrix.release)] }}
41 |
42 | steps:
43 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
44 | with:
45 | fetch-depth: 0
46 | submodules: recursive
47 |
48 | - name: Setup .NET SDK
49 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
50 | env:
51 | NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52 | with:
53 | global-json-file: global.json
54 | source-url: https://nuget.pkg.github.com/open-feature/index.json
55 |
56 | - name: Install dependencies
57 | run: dotnet restore
58 |
59 | - name: Build
60 | run: |
61 | dotnet build --configuration Release --no-restore -p:Deterministic=true
62 |
63 | - name: Pack
64 | run: |
65 | dotnet pack --configuration Release --no-build
66 |
67 | - name: Publish to Nuget
68 | run: |
69 | dotnet nuget push "${{ matrix.release }}/**/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_TOKEN }}
70 |
71 | - name: Generate artifact attestation
72 | uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
73 | with:
74 | subject-path: "${{ matrix.release }}/**/*.nupkg"
75 |
76 | - name: Find csproj file and set as environment variable
77 | id: find-csproj
78 | run: |
79 | csproj_path=$(find "${{ matrix.release }}" -name '*.csproj' -type f -print -quit)
80 | echo "CSPROJ_PATH=$csproj_path" >> "$GITHUB_OUTPUT"
81 |
82 | - name: Generate JSON SBOM
83 | uses: CycloneDX/gh-dotnet-generate-sbom@c183e4ac30e5b99354cb9a98c38548e07c538346 # v1.0.1
84 | with:
85 | path: ${{ steps.find-csproj.outputs.CSPROJ_PATH }}
86 | json: true
87 | github-bearer-token: ${{ secrets.GITHUB_TOKEN }}
88 |
89 | - name: Attest package
90 | uses: actions/attest-sbom@115c3be05ff3974bcbd596578934b3f9ce39bf68 # v2.2.0
91 | with:
92 | subject-path: "${{ matrix.release }}/**/*.nupkg"
93 | sbom-path: bom.json
94 |
95 | - name: Attach SBOM to artifact
96 | env:
97 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
98 | run: gh release upload ${{ env.TAG_NAME }} bom.json
99 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "src/OpenFeature.Contrib.Providers.Flagd/schemas"]
2 | path = src/OpenFeature.Contrib.Providers.Flagd/schemas
3 | url = https://github.com/open-feature/schemas.git
4 | [submodule "spec"]
5 | path = spec
6 | url = https://github.com/open-feature/spec.git
7 | [submodule "src/OpenFeature.Contrib.Providers.Flagd/flagd-testbed"]
8 | path = src/OpenFeature.Contrib.Providers.Flagd/flagd-testbed
9 | url = https://github.com/open-feature/flagd-testbed.git
10 |
--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "src/OpenFeature.Contrib.Hooks.Otel": "0.2.0",
3 | "src/OpenFeature.Contrib.Providers.Flagd": "0.3.2",
4 | "src/OpenFeature.Contrib.Providers.GOFeatureFlag": "0.2.1",
5 | "src/OpenFeature.Contrib.Providers.Flagsmith": "0.2.1",
6 | "src/OpenFeature.Contrib.Providers.ConfigCat": "0.1.1",
7 | "src/OpenFeature.Contrib.Providers.FeatureManagement": "0.1.1",
8 | "src/OpenFeature.Contrib.Providers.Statsig": "0.1.0",
9 | "src/OpenFeature.Contrib.Providers.Flipt": "0.0.5",
10 | "src/OpenFeature.Contrib.Providers.EnvVar": "0.0.7"
11 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet.defaultSolution": "DotnetSdkContrib.slnx"
3 | }
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## System Requirements
4 |
5 | Dotnet 8+ is recommended.
6 |
7 | ## Adding a project
8 |
9 | 1. Create a new library project under `src/`: `dotnet new classlib -o src/OpenFeature.Contrib.MyComponent`
10 | 2. Create a new test project under `test/`: `dotnet new xunit -o test/OpenFeature.Contrib.MyComponent.Test`
11 | 3. Add the library project to the solution: `dotnet sln DotnetSdkContrib.slnx add src/OpenFeature.Contrib.MyComponent/OpenFeature.Contrib.MyComponent.csproj`
12 | 4. Add the test project to the solution: `dotnet sln DotnetSdkContrib.slnx add test/OpenFeature.Contrib.MyComponent.Test/OpenFeature.Contrib.MyComponent.Test.csproj`
13 | 5. Add the desired properties to your library's `.csproj` file (see example below).
14 | 6. Remove all content besides the root element from your test project's `.csproj` file (all settings will be inherited).
15 | 7. Add the new library project to `release-please-config.json`.
16 | 8. Add a `version.txt` file to the root of your library with a version matching that in your new `.csproj` file, e.g. `0.0.1`.
17 | 9. If you care to release a pre `1.0.0` version, add the same version above to `.release-please-manifest.json`. Failing to do this will release a `1.0.0` initial release.
18 |
19 | Sample `.csproj` file:
20 |
21 | ```xml
22 |
23 |
24 |
25 | OpenFeature.Contrib.MyComponent
26 | 0.0.1
27 | $(VersionNumber)
28 | $(VersionNumber)
29 | $(VersionNumber)
30 | A very valuable OpenFeature contribution!
31 | Me!
32 |
33 |
34 |
35 | ```
36 |
37 | ## Documentation
38 |
39 | Any published modules must have documentation in their root directory, explaining the basic purpose of the module as well as installation and usage instructions.
40 | Instructions for how to develop a module should also be included (required system dependencies, instructions for testing locally, etc).
41 |
42 | ## Testing
43 |
44 | Any published modules must have reasonable test coverage.
45 | The instructions above will generate a test project for you.
46 |
47 | Use `dotnet test` to test the entire project.
48 |
49 | ## Versioning and releasing
50 |
51 | This repo uses _Release Please_ to release packages. Release Please sets up a running PR that tracks all changes for the library components, and maintains the versions according to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/), generated when [PRs are merged](https://github.com/amannn/action-semantic-pull-request). When Release Please's running PR is merged, any changed artifacts are published.
52 |
53 | ## Dependencies
54 |
55 | Keep dependencies to a minimum.
56 | Dependencies used only for building and testing should have a `all` element to prevent them from being exposed to consumers.
57 |
58 | ## Consuming pre-release packages
59 |
60 | 1. Acquire a [GitHub personal access token (PAT)](https://docs.github.com/github/authenticating-to-github/creating-a-personal-access-token) scoped for `read:packages` and verify the permissions:
61 |
62 | ```console
63 | $ gh auth login --scopes read:packages
64 |
65 | ? What account do you want to log into? GitHub.com
66 | ? What is your preferred protocol for Git operations? HTTPS
67 | ? How would you like to authenticate GitHub CLI? Login with a web browser
68 |
69 | ! First copy your one-time code: ****-****
70 | Press Enter to open github.com in your browser...
71 |
72 | ✓ Authentication complete.
73 | - gh config set -h github.com git_protocol https
74 | ✓ Configured git protocol
75 | ✓ Logged in as ********
76 | ```
77 |
78 | ```console
79 | $ gh auth status
80 |
81 | github.com
82 | ✓ Logged in to github.com as ******** (~/.config/gh/hosts.yml)
83 | ✓ Git operations for github.com configured to use https protocol.
84 | ✓ Token: gho_************************************
85 | ✓ Token scopes: gist, read:org, read:packages, repo, workflow
86 | ```
87 |
88 | 2. Run the following command to configure your local environment to consume packages from GitHub Packages:
89 |
90 | ```console
91 | $ dotnet nuget update source github-open-feature --username $(gh api user --jq .email) --password $(gh auth token) --store-password-in-clear-text
92 |
93 | Package source "github-open-feature" was successfully updated.
94 | ```
95 |
--------------------------------------------------------------------------------
/DotnetSdkContrib.slnx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OpenFeature .NET Contributions
2 |
3 | This repository is intended for OpenFeature contributions which are not included in the [OpenFeature SDK](https://github.com/open-feature/dotnet-sdk).
4 |
5 | ## Contributing
6 |
7 | Interested in contributing? Great, we'd love your help! To get started, take a look at the [CONTRIBUTING.md](./CONTRIBUTING.md) guide.
8 |
9 | ## Useful links
10 |
11 | * For more information on OpenFeature, visit [openfeature.dev](https://openfeature.dev)
12 | * For help or feedback on this project, join us on [Slack][slack] or create a [GitHub issue][github-issue].
13 |
14 | ## License
15 |
16 | [Apache License 2.0](./LICENSE)
17 |
18 | [github-issue]: https://github.com/open-feature/dotnet-sdk-contrib/issues/new
19 | [slack]: https://cloud-native.slack.com/archives/C0344AANLA1
20 |
--------------------------------------------------------------------------------
/build/Common.prod.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 | true
7 | true
8 | true
9 |
10 |
11 |
12 | netstandard2.0;net462;net8.0;net9.0
13 | git
14 | https://github.com/open-feature/dotnet-sdk-contrib
15 | OpenFeature is an open specification that provides a vendor-agnostic,
16 | community-driven API for feature flagging that works with your favorite feature flag
17 | management tool or in-house solution.
18 | Feature;OpenFeature;Flags;
19 | openfeature-icon.png
20 | https://openfeature.dev
21 | Apache-2.0
22 | OpenFeature Authors
23 | true
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/build/Common.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | latest
4 | true
5 | true
6 | all
7 | low
8 |
9 |
10 |
11 | full
12 | true
13 |
14 |
15 |
16 | true
17 |
18 |
19 |
20 |
22 |
23 |
--------------------------------------------------------------------------------
/build/Common.tests.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | $(NoWarn);1591
6 | false
7 | net8.0;net9.0
8 | $(TargetFrameworks);net462
9 |
10 |
11 |
12 | true
13 |
14 |
15 |
16 | $(NoWarn);CA2007
17 |
18 |
19 |
20 |
22 | PreserveNewest
23 |
24 |
25 |
26 |
27 |
28 |
29 | all
30 | runtime; build; native; contentfiles; analyzers; buildtransitive
31 |
32 |
33 | all
34 | runtime; build; native; contentfiles; analyzers; buildtransitive
35 |
36 |
37 |
38 |
39 |
40 |
42 | runtime; build; native; contentfiles; analyzers; buildtransitive
43 | all
44 |
45 |
46 |
48 |
49 |
51 |
52 |
53 |
54 |
58 | [4.18.1]
59 | [3.1.2]
60 | [2.2.1]
61 | [2.3.3]
62 | [17.13.0]
63 | [5.0.0]
64 | [4.3.1]
65 | [2.8.2,3.0)
66 | [2.9.3,3.0)
67 |
68 |
--------------------------------------------------------------------------------
/build/openfeature-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-feature/dotnet-sdk-contrib/741bfc0d8c9a2ab33bfe658b2279cbaae018779f/build/openfeature-icon.png
--------------------------------------------------------------------------------
/build/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "maxParallelThreads": 1,
3 | "parallelizeTestCollections": false
4 | }
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "rollForward": "latestFeature",
4 | "version": "9.0.203",
5 | "allowPrerelease": false
6 | }
7 | }
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "github>open-feature/community-tooling"
5 | ],
6 | "dependencyDashboardApproval": true,
7 | "recreateWhen": "never"
8 | }
9 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Hooks.Otel/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.2.0](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.1.8...OpenFeature.Contrib.Hooks.Otel-v0.2.0) (2024-08-22)
4 |
5 |
6 | ### ⚠ BREAKING CHANGES
7 |
8 | * use (and require) OpenFeature SDK v2 ([#262](https://github.com/open-feature/dotnet-sdk-contrib/issues/262))
9 |
10 | ### ✨ New Features
11 |
12 | * use (and require) OpenFeature SDK v2 ([#262](https://github.com/open-feature/dotnet-sdk-contrib/issues/262)) ([f845134](https://github.com/open-feature/dotnet-sdk-contrib/commit/f84513438586457087ac47fd40629912f2ec473a))
13 |
14 | ## [0.1.8](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.1.7...OpenFeature.Contrib.Hooks.Otel-v0.1.8) (2024-08-21)
15 |
16 |
17 | ### 🐛 Bug Fixes
18 |
19 | * version expression ([cad2cd1](https://github.com/open-feature/dotnet-sdk-contrib/commit/cad2cd166d0c25753b37189f044c3a585cda0fad))
20 |
21 | ## [0.1.7](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.1.6...OpenFeature.Contrib.Hooks.Otel-v0.1.7) (2024-08-20)
22 |
23 |
24 | ### 🧹 Chore
25 |
26 | * update OpenFeature version compatiblity ([#249](https://github.com/open-feature/dotnet-sdk-contrib/issues/249)) ([232e948](https://github.com/open-feature/dotnet-sdk-contrib/commit/232e948a0916ca10612f85343e2eecebca107090))
27 |
28 | ## [0.1.6](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.1.5...OpenFeature.Contrib.Hooks.Otel-v0.1.6) (2024-07-25)
29 |
30 |
31 | ### 🧹 Chore
32 |
33 | * **deps:** update dependency opentelemetry.api to v1.9.0 ([#170](https://github.com/open-feature/dotnet-sdk-contrib/issues/170)) ([258fe9a](https://github.com/open-feature/dotnet-sdk-contrib/commit/258fe9aadbc8dfe834accf4dbbe82ff601abd73d))
34 |
35 | ## [0.1.5](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.1.4...OpenFeature.Contrib.Hooks.Otel-v0.1.5) (2024-07-19)
36 |
37 |
38 | ### 🧹 Chore
39 |
40 | * **deps:** update dependency opentelemetry.api to v1.7.0 ([#65](https://github.com/open-feature/dotnet-sdk-contrib/issues/65)) ([533dfa6](https://github.com/open-feature/dotnet-sdk-contrib/commit/533dfa652011b645d48c5d17dbdfe39b64a8eadf))
41 |
42 | ## [0.1.4](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.1.3...OpenFeature.Contrib.Hooks.Otel-v0.1.4) (2024-01-25)
43 |
44 |
45 | ### 🧹 Chore
46 |
47 | * Add support for GitHub Packages ([#134](https://github.com/open-feature/dotnet-sdk-contrib/issues/134)) ([0def0da](https://github.com/open-feature/dotnet-sdk-contrib/commit/0def0da173e2f327b7381eba043b6e99ae8f26fe))
48 |
49 | ## [0.1.3](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.1.2...OpenFeature.Contrib.Hooks.Otel-v0.1.3) (2024-01-11)
50 |
51 |
52 | ### 🔄 Refactoring
53 |
54 | * Reduce OTel dependencies ([#132](https://github.com/open-feature/dotnet-sdk-contrib/issues/132)) ([de86b0e](https://github.com/open-feature/dotnet-sdk-contrib/commit/de86b0e34ea829608360109a5c5f1c61f8efdcaf))
55 |
56 | ## [0.1.2](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.1.1...OpenFeature.Contrib.Hooks.Otel-v0.1.2) (2023-12-19)
57 |
58 |
59 | ### ✨ New Features
60 |
61 | * Add Metrics Hook ([#114](https://github.com/open-feature/dotnet-sdk-contrib/issues/114)) ([5845e2b](https://github.com/open-feature/dotnet-sdk-contrib/commit/5845e2b0ae4b89a8a313051b42e6afdd856f1ea3))
62 |
63 |
64 | ### 🔄 Refactoring
65 |
66 | * Replace OtelHook with TracingHook. Deprecating OtelHook ([#116](https://github.com/open-feature/dotnet-sdk-contrib/issues/116)) ([755c549](https://github.com/open-feature/dotnet-sdk-contrib/commit/755c54960bccac97f6836ea8371d75bc2f1a02bb))
67 |
68 | ## [0.1.1](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.1.0...OpenFeature.Contrib.Hooks.Otel-v0.1.1) (2023-03-22)
69 |
70 |
71 | ### Features
72 |
73 | * added OTel hook ([#44](https://github.com/open-feature/dotnet-sdk-contrib/issues/44)) ([cbe92b5](https://github.com/open-feature/dotnet-sdk-contrib/commit/cbe92b52a3f58279d57a054bed368b6003e03561))
74 |
75 | ## [0.1.0](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Hooks.Otel-v0.0.2...OpenFeature.Contrib.Hooks.Otel-v0.1.0) (2022-10-16)
76 |
77 |
78 | ### ⚠ BREAKING CHANGES
79 |
80 | * rename namespace, add OpenFeature dependency and readmes.
81 | * rename namespace, add OpenFeature dep (#18)
82 |
83 | ### Features
84 |
85 | * rename namespace, add OpenFeature dep ([#18](https://github.com/open-feature/dotnet-sdk-contrib/issues/18)) ([3ca3172](https://github.com/open-feature/dotnet-sdk-contrib/commit/3ca31722b83053d4edf2038889c78efa717a7cff))
86 | * rename namespace, add OpenFeature dependency and readmes. ([3ca3172](https://github.com/open-feature/dotnet-sdk-contrib/commit/3ca31722b83053d4edf2038889c78efa717a7cff))
87 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Hooks.Otel/MetricsConstants.cs:
--------------------------------------------------------------------------------
1 | namespace OpenFeature.Contrib.Hooks.Otel;
2 |
3 | internal static class MetricsConstants
4 | {
5 | internal const string ActiveCountName = "feature_flag.evaluation_active_count";
6 | internal const string RequestsTotalName = "feature_flag.evaluation_requests_total";
7 | internal const string SuccessTotalName = "feature_flag.evaluation_success_total";
8 | internal const string ErrorTotalName = "feature_flag.evaluation_error_total";
9 |
10 | internal const string ActiveDescription = "active flag evaluations counter";
11 | internal const string RequestsDescription = "feature flag evaluation request counter";
12 | internal const string SuccessDescription = "feature flag evaluation success counter";
13 | internal const string ErrorDescription = "feature flag evaluation error counter";
14 |
15 | internal const string KeyAttr = "key";
16 | internal const string ProviderNameAttr = "provider_name";
17 | internal const string VariantAttr = "variant";
18 | internal const string ReasonAttr = "reason";
19 | internal const string ExceptionAttr = "exception";
20 | }
21 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Diagnostics.Metrics;
5 | using System.Reflection;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using OpenFeature.Model;
9 |
10 | namespace OpenFeature.Contrib.Hooks.Otel;
11 |
12 | ///
13 | /// Represents a hook for capturing metrics related to flag evaluations.
14 | /// The meter name is "OpenFeature.Contrib.Hooks.Otel".
15 | ///
16 | public class MetricsHook : Hook
17 | {
18 | private static readonly AssemblyName AssemblyName = typeof(MetricsHook).Assembly.GetName();
19 | private static readonly string InstrumentationName = AssemblyName.Name;
20 | private static readonly string InstrumentationVersion = AssemblyName.Version?.ToString();
21 |
22 | private readonly UpDownCounter _evaluationActiveUpDownCounter;
23 | private readonly Counter _evaluationRequestCounter;
24 | private readonly Counter _evaluationSuccessCounter;
25 | private readonly Counter _evaluationErrorCounter;
26 |
27 | ///
28 | /// Initializes a new instance of the class.
29 | ///
30 | public MetricsHook()
31 | {
32 | var meter = new Meter(InstrumentationName, InstrumentationVersion);
33 |
34 | _evaluationActiveUpDownCounter = meter.CreateUpDownCounter(MetricsConstants.ActiveCountName, description: MetricsConstants.ActiveDescription);
35 | _evaluationRequestCounter = meter.CreateCounter(MetricsConstants.RequestsTotalName, "{request}", MetricsConstants.RequestsDescription);
36 | _evaluationSuccessCounter = meter.CreateCounter(MetricsConstants.SuccessTotalName, "{impression}", MetricsConstants.SuccessDescription);
37 | _evaluationErrorCounter = meter.CreateCounter(MetricsConstants.ErrorTotalName, description: MetricsConstants.ErrorDescription);
38 | }
39 |
40 | ///
41 | public override ValueTask BeforeAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default)
42 | {
43 | var tagList = new TagList
44 | {
45 | { MetricsConstants.KeyAttr, context.FlagKey },
46 | { MetricsConstants.ProviderNameAttr, context.ProviderMetadata.Name }
47 | };
48 |
49 | _evaluationActiveUpDownCounter.Add(1, tagList);
50 | _evaluationRequestCounter.Add(1, tagList);
51 |
52 | return base.BeforeAsync(context, hints);
53 | }
54 |
55 |
56 | ///
57 | public override ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default)
58 | {
59 | var tagList = new TagList
60 | {
61 | { MetricsConstants.KeyAttr, context.FlagKey },
62 | { MetricsConstants.ProviderNameAttr, context.ProviderMetadata.Name },
63 | { MetricsConstants.VariantAttr, details.Variant ?? details.Value?.ToString() },
64 | { MetricsConstants.ReasonAttr, details.Reason ?? "UNKNOWN" }
65 | };
66 |
67 | _evaluationSuccessCounter.Add(1, tagList);
68 |
69 | return base.AfterAsync(context, details, hints);
70 | }
71 |
72 | ///
73 | public override ValueTask ErrorAsync(HookContext context, Exception error, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default)
74 | {
75 | var tagList = new TagList
76 | {
77 | { MetricsConstants.KeyAttr, context.FlagKey },
78 | { MetricsConstants.ProviderNameAttr, context.ProviderMetadata.Name },
79 | { MetricsConstants.ExceptionAttr, error?.Message ?? "Unknown error" }
80 | };
81 |
82 | _evaluationErrorCounter.Add(1, tagList);
83 |
84 | return base.ErrorAsync(context, error, hints);
85 | }
86 |
87 | ///
88 | public override ValueTask FinallyAsync(HookContext context, IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default)
89 | {
90 | var tagList = new TagList
91 | {
92 | { MetricsConstants.KeyAttr, context.FlagKey },
93 | { MetricsConstants.ProviderNameAttr, context.ProviderMetadata.Name }
94 | };
95 |
96 | _evaluationActiveUpDownCounter.Add(-1, tagList);
97 |
98 | return base.FinallyAsync(context, hints);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Hooks.Otel/OpenFeature.Contrib.Hooks.Otel.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OpenFeature.Contrib.Hooks.Otel
5 | 0.2.0
6 | $(VersionNumber)
7 | $(VersionNumber)
8 | $(VersionNumber)
9 | Open Telemetry Hook for .NET
10 | Florian Bacher
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Hooks.Otel/OtelHook.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using OpenFeature.Model;
7 |
8 | namespace OpenFeature.Contrib.Hooks.Otel;
9 |
10 |
11 | ///
12 | /// Stub.
13 | ///
14 | [ExcludeFromCodeCoverage]
15 | [Obsolete("This class is obsolete and will be removed in a future version. Please use TracingHook instead.")]
16 | public class OtelHook : Hook
17 | {
18 | private readonly TracingHook _tracingHook = new TracingHook();
19 |
20 | ///
21 | public override ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details,
22 | IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default)
23 | {
24 | _tracingHook.AfterAsync(context, details, hints);
25 |
26 | return default;
27 | }
28 |
29 | ///
30 | public override ValueTask ErrorAsync(HookContext context, Exception error,
31 | IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default)
32 | {
33 | _tracingHook.ErrorAsync(context, error, hints);
34 |
35 | return default;
36 | }
37 |
38 | }
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Hooks.Otel/TracingHook.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using OpenFeature.Model;
6 | using OpenTelemetry.Trace;
7 |
8 | namespace OpenFeature.Contrib.Hooks.Otel;
9 |
10 |
11 | ///
12 | /// Stub.
13 | ///
14 | public class TracingHook : Hook
15 | {
16 |
17 | ///
18 | public override ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details,
19 | IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default)
20 | {
21 | Activity.Current?
22 | .SetTag("feature_flag.key", details.FlagKey)
23 | .SetTag("feature_flag.variant", details.Variant)
24 | .SetTag("feature_flag.provider_name", context.ProviderMetadata.Name)
25 | .AddEvent(new ActivityEvent("feature_flag", tags: new ActivityTagsCollection
26 | {
27 | ["feature_flag.key"] = details.FlagKey,
28 | ["feature_flag.variant"] = details.Variant,
29 | ["feature_flag.provider_name"] = context.ProviderMetadata.Name
30 | }));
31 |
32 | return default;
33 | }
34 |
35 | ///
36 | public override ValueTask ErrorAsync(HookContext context, System.Exception error,
37 | IReadOnlyDictionary hints = null, CancellationToken cancellationToken = default)
38 | {
39 | Activity.Current?.RecordException(error);
40 |
41 | return default;
42 | }
43 |
44 | }
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Hooks.Otel/assets/otlp-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-feature/dotnet-sdk-contrib/741bfc0d8c9a2ab33bfe658b2279cbaae018779f/src/OpenFeature.Contrib.Hooks.Otel/assets/otlp-error.png
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Hooks.Otel/assets/otlp-success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-feature/dotnet-sdk-contrib/741bfc0d8c9a2ab33bfe658b2279cbaae018779f/src/OpenFeature.Contrib.Hooks.Otel/assets/otlp-success.png
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Hooks.Otel/version.txt:
--------------------------------------------------------------------------------
1 | 0.2.0
2 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.ConfigCat/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.1.1](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.ConfigCat-v0.1.0...OpenFeature.Contrib.Providers.ConfigCat-v0.1.1) (2024-09-17)
4 |
5 |
6 | ### 🐛 Bug Fixes
7 |
8 | * Revise ConfigCat provider ([#280](https://github.com/open-feature/dotnet-sdk-contrib/issues/280)) ([0b2d5f2](https://github.com/open-feature/dotnet-sdk-contrib/commit/0b2d5f29490ad16ee5efde55d31354e0322c6f86))
9 |
10 | ## [0.1.0](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.ConfigCat-v0.0.5...OpenFeature.Contrib.Providers.ConfigCat-v0.1.0) (2024-08-22)
11 |
12 |
13 | ### ⚠ BREAKING CHANGES
14 |
15 | * use (and require) OpenFeature SDK v2 ([#262](https://github.com/open-feature/dotnet-sdk-contrib/issues/262))
16 |
17 | ### ✨ New Features
18 |
19 | * use (and require) OpenFeature SDK v2 ([#262](https://github.com/open-feature/dotnet-sdk-contrib/issues/262)) ([f845134](https://github.com/open-feature/dotnet-sdk-contrib/commit/f84513438586457087ac47fd40629912f2ec473a))
20 |
21 | ## [0.0.5](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.ConfigCat-v0.0.4...OpenFeature.Contrib.Providers.ConfigCat-v0.0.5) (2024-08-21)
22 |
23 |
24 | ### 🐛 Bug Fixes
25 |
26 | * version expression ([cad2cd1](https://github.com/open-feature/dotnet-sdk-contrib/commit/cad2cd166d0c25753b37189f044c3a585cda0fad))
27 |
28 | ## [0.0.4](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.ConfigCat-v0.0.3...OpenFeature.Contrib.Providers.ConfigCat-v0.0.4) (2024-08-20)
29 |
30 |
31 | ### 🧹 Chore
32 |
33 | * update OpenFeature version compatiblity ([#249](https://github.com/open-feature/dotnet-sdk-contrib/issues/249)) ([232e948](https://github.com/open-feature/dotnet-sdk-contrib/commit/232e948a0916ca10612f85343e2eecebca107090))
34 |
35 | ## [0.0.3](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.ConfigCat-v0.0.2...OpenFeature.Contrib.Providers.ConfigCat-v0.0.3) (2024-06-27)
36 |
37 |
38 | ### 🧹 Chore
39 |
40 | * Add support for GitHub Packages ([#134](https://github.com/open-feature/dotnet-sdk-contrib/issues/134)) ([0def0da](https://github.com/open-feature/dotnet-sdk-contrib/commit/0def0da173e2f327b7381eba043b6e99ae8f26fe))
41 |
42 |
43 | ### 🔄 Refactoring
44 |
45 | * Use the newly introduced ConfigCat error codes to determine error type ([#201](https://github.com/open-feature/dotnet-sdk-contrib/issues/201)) ([a9eef05](https://github.com/open-feature/dotnet-sdk-contrib/commit/a9eef0559d2eb2ab53249c585ddae5ad74c98328))
46 |
47 | ## [0.0.2](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.ConfigCat-v0.0.1...OpenFeature.Contrib.Providers.ConfigCat-v0.0.2) (2024-01-16)
48 |
49 |
50 | ### ✨ New Features
51 |
52 | * Add ConfigCat provider ([#119](https://github.com/open-feature/dotnet-sdk-contrib/issues/119)) ([20aeb3a](https://github.com/open-feature/dotnet-sdk-contrib/commit/20aeb3a471227571fdc47a46a6292e0b59c9b3a5))
53 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.ConfigCat/OpenFeature.Contrib.Providers.ConfigCat.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OpenFeature.Contrib.Providers.ConfigCat
5 | 0.1.1
6 | $(VersionNumber)
7 | $(VersionNumber)
8 | $(VersionNumber)
9 | ConfigCat provider for .NET
10 | Luiz Bon
11 | true
12 |
13 |
14 |
15 |
16 | <_Parameter1>$(MSBuildProjectName).Test
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.ConfigCat/README.md:
--------------------------------------------------------------------------------
1 | # ConfigCat Feature Flag .NET Provider
2 |
3 | The ConfigCat Flag provider allows you to connect to your ConfigCat instance.
4 |
5 | # .NET SDK usage
6 |
7 | ## Requirements
8 |
9 | - open-feature/dotnet-sdk v1.5.0 > v2.0.0
10 |
11 | ## Install dependencies
12 |
13 | The first things we will do is install the **Open Feature SDK** and the **ConfigCat Feature Flag provider**.
14 |
15 | ### .NET Cli
16 | ```shell
17 | dotnet add package OpenFeature.Contrib.Providers.ConfigCat
18 | ```
19 | ### Package Manager
20 |
21 | ```shell
22 | NuGet\Install-Package OpenFeature.Contrib.Providers.ConfigCat
23 | ```
24 | ### Package Reference
25 |
26 | ```xml
27 |
28 | ```
29 | ### Packet cli
30 |
31 | ```shell
32 | paket add OpenFeature.Contrib.Providers.ConfigCat
33 | ```
34 |
35 | ### Cake
36 |
37 | ```shell
38 | // Install OpenFeature.Contrib.Providers.ConfigCat as a Cake Addin
39 | #addin nuget:?package=OpenFeature.Contrib.Providers.ConfigCat
40 |
41 | // Install OpenFeature.Contrib.Providers.ConfigCat as a Cake Tool
42 | #tool nuget:?package=OpenFeature.Contrib.Providers.ConfigCat
43 | ```
44 |
45 | ## Using the ConfigCat Provider with the OpenFeature SDK
46 |
47 | The following example shows how to use the ConfigCat provider with the OpenFeature SDK.
48 |
49 | ```csharp
50 | using System;
51 | using ConfigCat.Client;
52 | using OpenFeature.Contrib.ConfigCat;
53 |
54 | var configCatProvider = new ConfigCatProvider("#YOUR-SDK-KEY#");
55 |
56 | // Set the configCatProvider as the provider for the OpenFeature SDK
57 | await OpenFeature.Api.Instance.SetProviderAsync(configCatProvider);
58 |
59 | var client = OpenFeature.Api.Instance.GetClient();
60 |
61 | var isAwesomeFeatureEnabled = await client.GetBooleanValueAsync("isAwesomeFeatureEnabled", false);
62 | if (isAwesomeFeatureEnabled)
63 | {
64 | doTheNewThing();
65 | }
66 | else
67 | {
68 | doTheOldThing();
69 | }
70 | ```
71 |
72 | ### Customizing the ConfigCat Provider
73 |
74 | The ConfigCat provider can be customized by passing a callback setting up a `ConfigCatClientOptions` object to the constructor.
75 |
76 | ```csharp
77 | Action configureConfigCatOptions = (options) =>
78 | {
79 | options.PollingMode = PollingModes.LazyLoad(cacheTimeToLive: TimeSpan.FromSeconds(10));
80 | options.Logger = new ConsoleLogger(LogLevel.Info);
81 | // ...
82 | };
83 |
84 | var configCatProvider = new ConfigCatProvider("#YOUR-SDK-KEY#", configureConfigCatOptions);
85 | ```
86 |
87 | For a full list of options see the [ConfigCat documentation](https://configcat.com/docs/sdk-reference/dotnet/).
88 |
89 | ### Cleaning up
90 |
91 | On application shutdown, clean up the OpenFeature provider and the underlying ConfigCat client.
92 |
93 | ```csharp
94 | await OpenFeature.Api.Instance.ShutdownAsync();
95 | ```
96 |
97 | ## EvaluationContext and ConfigCat User Object relationship
98 |
99 | An evaluation context in the OpenFeature specification is a container for arbitrary contextual data that can be used as a basis for feature flag evaluation.
100 | The ConfigCat provider translates these evaluation contexts to ConfigCat [User Objects](https://configcat.com/docs/targeting/user-object/).
101 |
102 | The ConfigCat User Object has a few pre-defined attributes that can be used to evaluate a flag. These are:
103 |
104 | | Attribute | Description |
105 | |--------------|----------------------------------------------------------------------------------------------------------------|
106 | | `Identifier` | *REQUIRED*. Unique identifier of a user in your application. Can be any `string` value, even an email address. |
107 | | `Email` | The email address of the user. |
108 | | `Country` | The country of the user. |
109 |
110 | Since `EvaluationContext` is a simple dictionary, the provider will try to match the keys to ConfigCat user attributes following the table below in a case-insensitive manner.
111 |
112 | | EvaluationContext Key | ConfigCat User Attribute |
113 | |-----------------------|--------------------------|
114 | | `id` | `Identifier` |
115 | | `identifier` | `Identifier` |
116 | | `email` | `Email` |
117 | | `country` | `Country` |
118 | | Any other | `Custom` |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.ConfigCat/UserBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using ConfigCat.Client;
4 | using OpenFeature.Model;
5 |
6 | namespace OpenFeature.Contrib.ConfigCat;
7 |
8 | internal static class UserBuilder
9 | {
10 | private static readonly string[] PossibleUserIds = { "ID", "IDENTIFIER" };
11 |
12 | internal static User BuildUser(this EvaluationContext context)
13 | {
14 | if (context == null)
15 | {
16 | return null;
17 | }
18 |
19 | var user = new User(context.GetUserId());
20 |
21 | foreach (var value in context)
22 | {
23 | if (StringComparer.OrdinalIgnoreCase.Equals("EMAIL", value.Key))
24 | {
25 | user.Email = value.Value.AsString;
26 | }
27 | else if (StringComparer.OrdinalIgnoreCase.Equals("COUNTRY", value.Key))
28 | {
29 | user.Country = value.Value.AsString;
30 | }
31 | else
32 | {
33 | user.Custom.Add(value.Key, value.Value.AsString);
34 | }
35 | }
36 |
37 | return user;
38 | }
39 |
40 | private static string GetUserId(this EvaluationContext context)
41 | {
42 | var pair = context.AsDictionary().FirstOrDefault(x => PossibleUserIds.Contains(x.Key, StringComparer.OrdinalIgnoreCase));
43 |
44 | return pair.Key != null ? pair.Value.AsString : "";
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.EnvVar/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.0.7](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.EnvVar-v0.0.6...OpenFeature.Contrib.Providers.EnvVar-v0.0.7) (2025-05-08)
4 |
5 |
6 | ### 🧹 Chore
7 |
8 | * fix typo in readme ([#400](https://github.com/open-feature/dotnet-sdk-contrib/issues/400)) ([4ea76fc](https://github.com/open-feature/dotnet-sdk-contrib/commit/4ea76fc18474a054d6725d17833cce5d895714b1))
9 |
10 | ## [0.0.6](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.EnvVar-v0.0.5...OpenFeature.Contrib.Providers.EnvVar-v0.0.6) (2025-05-08)
11 |
12 |
13 | ### 🧹 Chore
14 |
15 | * relax doc requirements for test projects ([e204e16](https://github.com/open-feature/dotnet-sdk-contrib/commit/e204e168ee8ccda34f46de325d45447e3ef85f73))
16 |
17 | ## [0.0.5](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.EnvVar-v0.0.4...OpenFeature.Contrib.Providers.EnvVar-v0.0.5) (2025-05-08)
18 |
19 |
20 | ### 🧹 Chore
21 |
22 | * typo in readme ([e1c1450](https://github.com/open-feature/dotnet-sdk-contrib/commit/e1c1450e0c237764485d84e90a8573c2bcdb2edd))
23 |
24 | ## [0.0.4](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.EnvVar-v0.0.3...OpenFeature.Contrib.Providers.EnvVar-v0.0.4) (2025-05-08)
25 |
26 |
27 | ### 🧹 Chore
28 |
29 | * update README ([ed1c3ae](https://github.com/open-feature/dotnet-sdk-contrib/commit/ed1c3ae133df6a4ee6949dee3b3e441ef2dafea1))
30 |
31 | ## [0.0.3](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.EnvVar-v0.0.2...OpenFeature.Contrib.Providers.EnvVar-v0.0.3) (2025-05-06)
32 |
33 |
34 | ### 🧹 Chore
35 |
36 | * Add .editorconfig and cleanup code ([#375](https://github.com/open-feature/dotnet-sdk-contrib/issues/375)) ([683a392](https://github.com/open-feature/dotnet-sdk-contrib/commit/683a392604aca6c9a92b1f64fa30bc9e3e069b4f))
37 |
38 | ## [0.0.2](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.EnvVar-v0.0.1...OpenFeature.Contrib.Providers.EnvVar-v0.0.2) (2025-03-12)
39 |
40 |
41 | ### ✨ New Features
42 |
43 | * Environment Variable Provider ([#312](https://github.com/open-feature/dotnet-sdk-contrib/issues/312)) ([4908000](https://github.com/open-feature/dotnet-sdk-contrib/commit/4908000ed27a648ee7cf8823320ae7d7c8cd8c45))
44 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.EnvVar/EnvVarProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using OpenFeature.Constant;
5 | using OpenFeature.Error;
6 | using OpenFeature.Model;
7 |
8 | namespace OpenFeature.Contrib.Providers.EnvVar;
9 |
10 | ///
11 | /// An OpenFeature provider using environment variables.
12 | ///
13 | public sealed class EnvVarProvider : FeatureProvider
14 | {
15 | private const string Name = "Environment Variable Provider";
16 | private readonly string _prefix;
17 | private delegate bool TryConvert(string value, out TResult result);
18 |
19 | ///
20 | /// Creates a new instance of
21 | ///
22 | public EnvVarProvider() : this(string.Empty)
23 | {
24 | }
25 |
26 | ///
27 | /// Creates a new instance of
28 | ///
29 | /// A prefix which will be used when evaluating environment variables
30 | public EnvVarProvider(string prefix)
31 | {
32 | _prefix = prefix;
33 | }
34 |
35 | ///
36 | public override Metadata GetMetadata()
37 | {
38 | return new Metadata(Name);
39 | }
40 |
41 | private Task> Resolve(string flagKey, T defaultValue, TryConvert tryConvert)
42 | {
43 | var envVarName = $"{_prefix}{flagKey}";
44 | var value = Environment.GetEnvironmentVariable(envVarName);
45 |
46 | if (value == null)
47 | return Task.FromResult(new ResolutionDetails(flagKey, defaultValue, ErrorType.FlagNotFound, Reason.Error, string.Empty, $"Unable to find environment variable '{envVarName}'"));
48 |
49 | if (!tryConvert(value, out var convertedValue))
50 | throw new FeatureProviderException(ErrorType.TypeMismatch, $"Could not convert the value of environment variable '{envVarName}' to {typeof(T)}");
51 |
52 | return Task.FromResult(new ResolutionDetails(flagKey, convertedValue, ErrorType.None, Reason.Static));
53 | }
54 |
55 | ///
56 | public override Task> ResolveBooleanValueAsync(string flagKey, bool defaultValue, EvaluationContext context = null,
57 | CancellationToken cancellationToken = new CancellationToken())
58 | {
59 | return Resolve(flagKey, defaultValue, bool.TryParse);
60 | }
61 |
62 | ///
63 | public override Task> ResolveStringValueAsync(string flagKey, string defaultValue, EvaluationContext context = null,
64 | CancellationToken cancellationToken = new CancellationToken())
65 | {
66 | return Resolve(flagKey, defaultValue, NoopTryParse);
67 |
68 | bool NoopTryParse(string value, out string result)
69 | {
70 | result = value;
71 | return true;
72 | }
73 | }
74 |
75 | ///
76 | public override Task> ResolveIntegerValueAsync(string flagKey, int defaultValue, EvaluationContext context = null,
77 | CancellationToken cancellationToken = new CancellationToken())
78 | {
79 | return Resolve(flagKey, defaultValue, int.TryParse);
80 | }
81 |
82 | ///
83 | public override Task> ResolveDoubleValueAsync(string flagKey, double defaultValue, EvaluationContext context = null,
84 | CancellationToken cancellationToken = new CancellationToken())
85 | {
86 | return Resolve(flagKey, defaultValue, double.TryParse);
87 | }
88 |
89 | ///
90 | public override Task> ResolveStructureValueAsync(string flagKey, Value defaultValue, EvaluationContext context = null,
91 | CancellationToken cancellationToken = new CancellationToken())
92 | {
93 | return Resolve(flagKey, defaultValue, ConvertStringToValue);
94 |
95 | bool ConvertStringToValue(string s, out Value value)
96 | {
97 | value = new Value(s);
98 | return true;
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.EnvVar/OpenFeature.Contrib.Providers.EnvVar.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OpenFeature.Contrib.Providers.EnvVar
5 | 0.0.7
6 | $(VersionNumber)
7 | $(VersionNumber)
8 | $(VersionNumber)
9 | Environment Variable Provider for .NET
10 | Octopus Deploy
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.EnvVar/README.md:
--------------------------------------------------------------------------------
1 | # .NET Environment Variable Provider
2 |
3 | The environment Variable provider allows you to read feature flags from the [process's environment](https://en.wikipedia.org/wiki/Environment_variable).
4 |
5 | ## Installation
6 |
7 | ### .NET CLI
8 |
9 | ```shell
10 | dotnet add package OpenFeature.Contrib.Providers.EnvVar
11 | ```
12 |
13 | ## Using the Environment Variable Provider with the OpenFeature SDK
14 |
15 | The following example shows how to use the Environment Variable provider with the OpenFeature SDK.
16 |
17 | ```csharp
18 | using System;
19 | using OpenFeature;
20 | using OpenFeature.Contrib.EnvVar;
21 |
22 | // If you want to use a prefix for your environment variables, you can supply it in the constructor below.
23 | // For example, if you all your feature flag environment variables will be prefixed with feature-flag- then
24 | // you would use:
25 | // var envVarProvider = new EnvVarProvider("feature-flag-");
26 | var envVarProvider = new EnvVarProvider();
27 |
28 | // Set the Environment Variable provider as the provider for the OpenFeature SDK
29 | await OpenFeature.Api.Instance.SetProviderAsync(envVarProvider);
30 | var client = OpenFeature.Api.Instance.GetClient();
31 |
32 | var isAwesomeFeatureEnabled = await client.GetBooleanValueAsync("isAwesomeFeatureEnabled", false);
33 | if (isAwesomeFeatureEnabled)
34 | {
35 | doTheNewThing();
36 | }
37 | else
38 | {
39 | doTheOldThing();
40 | }
41 | ```
42 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.EnvVar/version.txt:
--------------------------------------------------------------------------------
1 | 0.0.7
2 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.FeatureManagement/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.1.1](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.FeatureManagement-v0.1.0...OpenFeature.Contrib.Providers.FeatureManagement-v0.1.1) (2025-05-30)
4 |
5 |
6 | ### ✨ New Features
7 |
8 | * Add support for basic flags in Feature Management ([#350](https://github.com/open-feature/dotnet-sdk-contrib/issues/350)) ([cfe5e57](https://github.com/open-feature/dotnet-sdk-contrib/commit/cfe5e5739edc0e812d3efcc01b740ecaad88d8a3))
9 |
10 |
11 | ### 🧹 Chore
12 |
13 | * Add .editorconfig and cleanup code ([#375](https://github.com/open-feature/dotnet-sdk-contrib/issues/375)) ([683a392](https://github.com/open-feature/dotnet-sdk-contrib/commit/683a392604aca6c9a92b1f64fa30bc9e3e069b4f))
14 | * **deps:** Upgrading to 4.0.0 of Microsoft.FeatureManagement ([#303](https://github.com/open-feature/dotnet-sdk-contrib/issues/303)) ([686cafc](https://github.com/open-feature/dotnet-sdk-contrib/commit/686cafc2c3b240736b61d0e32eec65b8449396c7))
15 | * relax doc requirements for test projects ([e204e16](https://github.com/open-feature/dotnet-sdk-contrib/commit/e204e168ee8ccda34f46de325d45447e3ef85f73))
16 |
17 | ## [0.1.0](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.FeatureManagement-v0.0.4...OpenFeature.Contrib.Providers.FeatureManagement-v0.1.0) (2024-08-22)
18 |
19 |
20 | ### ⚠ BREAKING CHANGES
21 |
22 | * use (and require) OpenFeature SDK v2 ([#262](https://github.com/open-feature/dotnet-sdk-contrib/issues/262))
23 |
24 | ### ✨ New Features
25 |
26 | * use (and require) OpenFeature SDK v2 ([#262](https://github.com/open-feature/dotnet-sdk-contrib/issues/262)) ([f845134](https://github.com/open-feature/dotnet-sdk-contrib/commit/f84513438586457087ac47fd40629912f2ec473a))
27 |
28 | ## [0.0.4](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.FeatureManagement-v0.0.3...OpenFeature.Contrib.Providers.FeatureManagement-v0.0.4) (2024-08-21)
29 |
30 |
31 | ### 🐛 Bug Fixes
32 |
33 | * version expression ([cad2cd1](https://github.com/open-feature/dotnet-sdk-contrib/commit/cad2cd166d0c25753b37189f044c3a585cda0fad))
34 |
35 | ## [0.0.3](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.FeatureManagement-v0.0.2...OpenFeature.Contrib.Providers.FeatureManagement-v0.0.3) (2024-08-20)
36 |
37 |
38 | ### 🧹 Chore
39 |
40 | * update OpenFeature version compatiblity ([#249](https://github.com/open-feature/dotnet-sdk-contrib/issues/249)) ([232e948](https://github.com/open-feature/dotnet-sdk-contrib/commit/232e948a0916ca10612f85343e2eecebca107090))
41 |
42 | ## [0.0.2](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.FeatureManagement-v0.0.1...OpenFeature.Contrib.Providers.FeatureManagement-v0.0.2) (2024-07-23)
43 |
44 |
45 | ### ✨ New Features
46 |
47 | * Adding a Provider implementation on top of the standard dotnet FeatureManagement system. ([#129](https://github.com/open-feature/dotnet-sdk-contrib/issues/129)) ([69bf2d6](https://github.com/open-feature/dotnet-sdk-contrib/commit/69bf2d67606affa334792e5a9c70da9e4a28748e))
48 |
49 |
50 | ### 🧹 Chore
51 |
52 | * add FeatureManagement documentation ([#197](https://github.com/open-feature/dotnet-sdk-contrib/issues/197)) ([01286c9](https://github.com/open-feature/dotnet-sdk-contrib/commit/01286c95228491707b2834fa2f2c4928c30800e4))
53 | * add release config for FeatureManagement provider ([#135](https://github.com/open-feature/dotnet-sdk-contrib/issues/135)) ([60e03f8](https://github.com/open-feature/dotnet-sdk-contrib/commit/60e03f8417508e4d18c7943dabfe52634742f51f))
54 | * Add support for GitHub Packages ([#134](https://github.com/open-feature/dotnet-sdk-contrib/issues/134)) ([0def0da](https://github.com/open-feature/dotnet-sdk-contrib/commit/0def0da173e2f327b7381eba043b6e99ae8f26fe))
55 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.FeatureManagement/OpenFeature.Contrib.Providers.FeatureManagement.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OpenFeature.Contrib.Provider.FeatureManagement
5 | 0.1.1
6 | $(VersionNumber)
7 | preview
8 | $(VersionNumber)
9 | $(VersionNumber)
10 | An OpenFeature Provider built on top of the standard Microsoft FeatureManagement
11 | Library
12 | Eric Pattison
13 | true
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.FeatureManagement/README.md:
--------------------------------------------------------------------------------
1 | # FeatureManagement .NET Provider
2 | > ![NOTE]
3 | > This requires a new feature of the FeatureManagement system, Variants. This feature is still in preview and has not been fully released.
4 |
5 | The FeatureManagement Provider allows you to use the FeatureManagement system as an OpenFeature Provider.
6 |
7 | ## Requirements
8 |
9 | - open-feature/dotnet-sdk v1.5.0 > v2.0.0
10 |
11 | ## .NET SDK Usage
12 |
13 | ### Install dependencies
14 |
15 |
16 | #### .NET Cli
17 |
18 | ```shell
19 | dotnet add package OpenFeature.Contrib.Provider.FeatureManagement --version 0.1.1
20 | ```
21 |
22 | #### Package Manager
23 |
24 | ```shell
25 | NuGet\Install-Package OpenFeature.Contrib.Provider.FeatureManagement -Version 0.1.1
26 | ```
27 |
28 | #### Package Reference
29 |
30 | ```xml
31 |
32 | ```
33 |
34 | #### Paket CLI
35 | ```shell
36 | paket add OpenFeature.Contrib.Provider.FeatureManagement --version 0.1.1
37 | ```
38 |
39 | #### Cake
40 |
41 | ```shell
42 | // Install OpenFeature.Contrib.Provider.FeatureManagement as a Cake Addin
43 | #addin nuget:?package=OpenFeature.Contrib.Provider.FeatureManagement&version=0.1.1&prerelease
44 |
45 | // Install OpenFeature.Contrib.Provider.FeatureManagement as a Cake Tool
46 | #tool nuget:?package=OpenFeature.Contrib.Provider.FeatureManagement&version=0.1.1&prerelease
47 | ```
48 |
49 |
50 | ### Using the FeatureManagement Provider with the OpenFeature SDK
51 |
52 | FeatureManagement is built on top of .NETs Configuration system, so you must provide the loaded Configuration.
53 | Since Configuration is passed in any valid Configuration source can be used.
54 | For simplicity, we'll stick with a json file for all examples.
55 |
56 | ```csharp
57 | namespace OpenFeatureTestApp
58 | {
59 | class Program
60 | {
61 | static void Main(string[] args)
62 | {
63 | var configurationBuilder = new ConfigurationBuilder();
64 | configurationBuilder.AddJsonFile("featureFlags.json");
65 |
66 | IConfiguration configuration = configurationBuilder.Build();
67 |
68 | var featureManagementProvider = new FeatureManagementProvider(configuration);
69 | OpenFeature.Api.Instance.SetProvider(featureManagementProvider);
70 |
71 | var client = OpenFeature.Api.Instance.GetClient();
72 |
73 | var val = await client.GetBooleanValueAsync("myBoolFlag", false, null);
74 |
75 | System.Console.WriteLine(val.ToString());
76 | }
77 | }
78 | }
79 | ```
80 |
81 | A simple example configuration would look like this.
82 |
83 | ```json
84 | {
85 | "FeatureManagement": {
86 | "myBoolFlag": {
87 | "Allocation": {
88 | "DefaultWhenEnabled": "FlagEnabled",
89 | "DefaultWhenDisabled": "FlagDisabled"
90 | },
91 | "Variants": [
92 | {
93 | "Name": "FlagEnabled",
94 | "ConfigurationValue": true
95 | },
96 | {
97 | "Name": "FlagDisabled",
98 | "ConfigurationValue": false
99 | }
100 | ],
101 | "EnabledFor": [
102 | {
103 | "Name": "AlwaysOn"
104 | }
105 | ]
106 | }
107 | }
108 | }
109 | ```
110 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.FeatureManagement/version.txt:
--------------------------------------------------------------------------------
1 | 0.1.1
2 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/Cache.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Runtime.CompilerServices;
3 |
4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
5 |
6 | namespace OpenFeature.Contrib.Providers.Flagd;
7 |
8 | internal interface ICache
9 | {
10 | void Add(TKey key, TValue value);
11 | TValue TryGet(TKey key);
12 | void Delete(TKey key);
13 | void Purge();
14 | }
15 | class LRUCache : ICache where TValue : class
16 | {
17 | private readonly int _capacity;
18 | private readonly Dictionary _map;
19 | private Node _head;
20 | private Node _tail;
21 |
22 | private System.Threading.Mutex _mtx;
23 |
24 | public LRUCache(int capacity)
25 | {
26 | _capacity = capacity;
27 | _map = new Dictionary();
28 | _mtx = new System.Threading.Mutex();
29 | }
30 |
31 | public TValue TryGet(TKey key)
32 | {
33 | using (var mtx = new Mutex(ref _mtx))
34 | {
35 | mtx.Lock();
36 | if (_map.TryGetValue(key, out Node node))
37 | {
38 | MoveToFront(node);
39 | return node.Value;
40 | }
41 | return default(TValue);
42 | }
43 | }
44 |
45 | public void Add(TKey key, TValue value)
46 | {
47 | using (var mtx = new Mutex(ref _mtx))
48 | {
49 | mtx.Lock();
50 | if (_map.TryGetValue(key, out Node node))
51 | {
52 | node.Value = value;
53 | MoveToFront(node);
54 | }
55 | else
56 | {
57 | if (_map.Count >= _capacity)
58 | {
59 | _map.Remove(_tail.Key);
60 | RemoveTail();
61 | }
62 | node = new Node(key, value);
63 | _map.Add(key, node);
64 | AddToFront(node);
65 | }
66 | }
67 | }
68 |
69 | public void Delete(TKey key)
70 | {
71 | using (var mtx = new Mutex(ref _mtx))
72 | {
73 | mtx.Lock();
74 | if (_map.TryGetValue(key, out Node node))
75 | {
76 | if (node == _head)
77 | {
78 | _head = node.Next;
79 | }
80 | else
81 | {
82 | node.Prev.Next = node.Next;
83 | }
84 | if (node.Next != null)
85 | {
86 | node.Next.Prev = node.Prev;
87 | }
88 | _map.Remove(key);
89 | }
90 | }
91 | }
92 |
93 | public void Purge()
94 | {
95 | using (var mtx = new Mutex(ref _mtx))
96 | {
97 | mtx.Lock();
98 | _map.Clear();
99 | }
100 | }
101 |
102 | private void MoveToFront(Node node)
103 | {
104 | if (node == _head)
105 | return;
106 | node.Prev.Next = node.Next;
107 | if (node == _tail)
108 | _tail = node.Prev;
109 | else
110 | node.Next.Prev = node.Prev;
111 | AddToFront(node);
112 | }
113 |
114 | private void AddToFront(Node node)
115 | {
116 | if (_head == null)
117 | {
118 | _head = node;
119 | _tail = node;
120 | return;
121 | }
122 | node.Next = _head;
123 | _head.Prev = node;
124 | _head = node;
125 | }
126 |
127 | private void RemoveTail()
128 | {
129 | _tail = _tail.Prev;
130 | if (_tail != null)
131 | _tail.Next = null;
132 | else
133 | _head = null;
134 | }
135 |
136 | private class Node
137 | {
138 | public TKey Key;
139 | public TValue Value;
140 | public Node Next;
141 | public Node Prev;
142 |
143 | public Node(TKey key, TValue value)
144 | {
145 | Key = key;
146 | Value = value;
147 | }
148 | }
149 |
150 | private class Mutex : System.IDisposable
151 | {
152 |
153 | public System.Threading.Mutex _mtx;
154 |
155 | public Mutex(ref System.Threading.Mutex mtx)
156 | {
157 | _mtx = mtx;
158 | }
159 |
160 | public void Lock()
161 | {
162 | _mtx.WaitOne();
163 | }
164 |
165 | public void Dispose()
166 | {
167 | _mtx.ReleaseMutex();
168 | }
169 | }
170 | }
171 |
172 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/DependencyInjection/FeatureBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Logging;
4 | using Microsoft.Extensions.Logging.Abstractions;
5 | using Microsoft.Extensions.Options;
6 | using OpenFeature.Contrib.Providers.Flagd;
7 | using OpenFeature.Contrib.Providers.Flagd.DependencyInjection;
8 |
9 | namespace OpenFeature.DependencyInjection.Providers.Flagd;
10 |
11 | ///
12 | /// Extension methods for configuring the .
13 | ///
14 | public static class FeatureBuilderExtensions
15 | {
16 | ///
17 | /// Adds the to the with default configuration.
18 | ///
19 | /// The instance to configure.
20 | /// The instance for chaining.
21 | public static OpenFeatureBuilder AddFlagdProvider(this OpenFeatureBuilder builder)
22 | {
23 | builder.Services.AddOptions(FlagdProviderOptions.DefaultName);
24 | return builder.AddProvider(sp => CreateProvider(sp, null));
25 | }
26 |
27 | ///
28 | /// Adds the to the with configuration.
29 | ///
30 | /// The instance to configure.
31 | /// Options to configure .
32 | /// The instance for chaining.
33 | public static OpenFeatureBuilder AddFlagdProvider(this OpenFeatureBuilder builder, Action options)
34 | {
35 | builder.Services.Configure(FlagdProviderOptions.DefaultName, options);
36 | return builder.AddProvider(sp => CreateProvider(sp, null));
37 | }
38 |
39 | ///
40 | /// Adds the to the with a specific domain and default configuration.
41 | ///
42 | /// The instance to configure.
43 | /// The unique domain of the provider.
44 | /// The instance for chaining.
45 | public static OpenFeatureBuilder AddFlagdProvider(this OpenFeatureBuilder builder, string domain)
46 | {
47 | builder.Services.AddOptions(domain);
48 | return builder.AddProvider(domain, CreateProvider);
49 | }
50 |
51 | ///
52 | /// Adds the to the with a specific domain and configuration.
53 | ///
54 | /// The instance to configure.
55 | /// The unique domain of the provider.
56 | /// Options to configure .
57 | /// The instance for chaining.
58 | public static OpenFeatureBuilder AddFlagdProvider(this OpenFeatureBuilder builder, string domain, Action options)
59 | {
60 | builder.Services.Configure(domain, options);
61 | return builder.AddProvider(domain, CreateProvider);
62 | }
63 |
64 | private static FlagdProvider CreateProvider(IServiceProvider provider, string domain)
65 | {
66 | var optionsMonitor = provider.GetRequiredService>();
67 | var logger = provider.GetService>();
68 | logger ??= NullLogger.Instance;
69 |
70 | var options = string.IsNullOrEmpty(domain)
71 | ? optionsMonitor.Get(FlagdProviderOptions.DefaultName)
72 | : optionsMonitor.Get(domain);
73 |
74 | var config = options.ToFlagdConfig();
75 | config.Logger = logger;
76 |
77 | return new FlagdProvider(config);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/DependencyInjection/FlagdProviderOptions.cs:
--------------------------------------------------------------------------------
1 | using OpenFeature.Contrib.Providers.Flagd;
2 |
3 | namespace OpenFeature.DependencyInjection.Providers.Flagd;
4 |
5 | ///
6 | /// Configuration options for the Flagd provider.
7 | ///
8 | public record FlagdProviderOptions
9 | {
10 | ///
11 | /// Default name for the Flagd provider.
12 | ///
13 | public const string DefaultName = "FlagdProvider";
14 |
15 | ///
16 | /// The host for the provider to connect to. Defaults to "localhost".
17 | ///
18 | public string Host { get; set; } = "localhost";
19 |
20 | ///
21 | /// The Port property of the config. Defaults to 8013.
22 | ///
23 | public int Port { get; set; } = 8013;
24 |
25 | ///
26 | /// Use TLS for communication between the provider and the host. Defaults to false.
27 | ///
28 | public bool UseTls { get; set; } = false;
29 |
30 | ///
31 | /// Enable/disable the local cache for static flag values. Defaults to false.
32 | ///
33 | public bool CacheEnabled { get; set; } = false;
34 |
35 | ///
36 | /// The maximum size of the cache. Defaults to 10.
37 | ///
38 | public int MaxCacheSize { get; set; } = 10;
39 |
40 | ///
41 | /// Path to the certificate file. Defaults to empty string.
42 | ///
43 | public string CertificatePath { get; set; } = string.Empty;
44 |
45 | ///
46 | /// Path to the socket. Defaults to empty string.
47 | ///
48 | public string SocketPath { get; set; } = string.Empty;
49 |
50 | ///
51 | /// Maximum number of times the connection to the event stream should be re-attempted. Defaults to 3.
52 | ///
53 | public int MaxEventStreamRetries { get; set; } = 3;
54 |
55 | ///
56 | /// Which type of resolver to use. Defaults to .
57 | ///
58 | public ResolverType ResolverType { get; set; } = ResolverType.RPC;
59 |
60 | ///
61 | /// Source selector for the in-process provider. Defaults to empty string.
62 | ///
63 | public string SourceSelector { get; set; } = string.Empty;
64 | }
65 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/DependencyInjection/FlagdProviderOptionsExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using OpenFeature.DependencyInjection.Providers.Flagd;
3 |
4 | namespace OpenFeature.Contrib.Providers.Flagd.DependencyInjection;
5 |
6 | internal static class FlagdProviderOptionsExtensions
7 | {
8 | public static FlagdConfig ToFlagdConfig(this FlagdProviderOptions options)
9 | {
10 | if (options == null)
11 | {
12 | throw new ArgumentNullException(nameof(options), "FlagdProviderOptions cannot be null.");
13 | }
14 |
15 | var config = FlagdConfig.Builder()
16 | .WithHost(options.Host)
17 | .WithPort(options.Port)
18 | .WithTls(options.UseTls)
19 | .WithCache(options.CacheEnabled)
20 | .WithMaxCacheSize(options.MaxCacheSize)
21 | .WithCertificatePath(options.CertificatePath)
22 | .WithSocketPath(options.SocketPath)
23 | .WithMaxEventStreamRetries(options.MaxEventStreamRetries)
24 | .WithResolverType(options.ResolverType)
25 | .WithSourceSelector(options.SourceSelector)
26 | .Build();
27 |
28 | return config;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/OpenFeature.Contrib.Providers.Flagd.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OpenFeature.Contrib.Providers.Flagd
5 | 0.3.2
6 | $(VersionNumber)
7 | $(VersionNumber)
8 | $(VersionNumber)
9 | flagd provider for .NET
10 | Todd Baert
11 | true
12 |
13 |
14 |
15 |
16 |
17 | <_Parameter1>$(MSBuildProjectName).Test
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | runtime; build; native; contentfiles; analyzers; buildtransitive
37 | all
38 |
39 |
40 |
41 |
42 |
43 |
44 | [2.2,2.99999]
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/CustomEvaluators/FlagdProperties.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Nodes;
3 | using Json.Logic;
4 |
5 | namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess.CustomEvaluators;
6 |
7 | internal sealed class FlagdProperties
8 | {
9 |
10 | internal const string FlagdPropertiesKey = "$flagd";
11 | internal const string FlagKeyKey = "flagKey";
12 | internal const string TimestampKey = "timestamp";
13 | internal const string TargetingKeyKey = "targetingKey";
14 |
15 | internal string FlagKey { get; set; }
16 | internal long Timestamp { get; set; }
17 | internal string TargetingKey { get; set; }
18 |
19 | internal FlagdProperties(EvaluationContext from)
20 | {
21 |
22 | if (from.TryFind(TargetingKeyKey, out JsonNode targetingKeyValue)
23 | && targetingKeyValue.GetValueKind() == JsonValueKind.String)
24 | {
25 | TargetingKey = targetingKeyValue.ToString();
26 | }
27 | if (from.TryFind($"{FlagdPropertiesKey}.{FlagKeyKey}", out JsonNode flagKeyValue)
28 | && flagKeyValue.GetValueKind() == JsonValueKind.String)
29 | {
30 | FlagKey = flagKeyValue.ToString();
31 | }
32 | if (from.TryFind($"{FlagdPropertiesKey}.{TimestampKey}", out JsonNode timestampValue)
33 | && timestampValue.GetValueKind() == JsonValueKind.Number)
34 | {
35 | Timestamp = timestampValue.GetValue();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/CustomEvaluators/FractionalRule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.Json;
6 | using System.Text.Json.Nodes;
7 | using Json.Logic;
8 | using Murmur;
9 |
10 | namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess.CustomEvaluators;
11 |
12 | ///
13 | internal sealed class FractionalEvaluator : IRule
14 | {
15 | class FractionalEvaluationDistribution
16 | {
17 | public string variant;
18 | public int weight;
19 | }
20 |
21 | ///
22 | public JsonNode Apply(JsonNode args, EvaluationContext context)
23 | {
24 | // check if we have at least two arguments:
25 | // 1. the property value
26 | // 2. the array containing the buckets
27 |
28 | if (args.AsArray().Count == 0)
29 | {
30 | return null;
31 | }
32 |
33 | var flagdProperties = new FlagdProperties(context);
34 |
35 | var bucketStartIndex = 0;
36 |
37 | var arg0 = JsonLogic.Apply(args[0], context);
38 |
39 | string propertyValue;
40 | if (arg0.GetValueKind() == JsonValueKind.String)
41 | {
42 | propertyValue = arg0.ToString();
43 | bucketStartIndex = 1;
44 | }
45 | else
46 | {
47 | propertyValue = flagdProperties.FlagKey + flagdProperties.TargetingKey;
48 | }
49 |
50 | var distributions = new List();
51 | var distributionSum = 0;
52 |
53 | for (var i = bucketStartIndex; i < args.AsArray().Count; i++)
54 | {
55 | var bucket = JsonLogic.Apply(args[i], context);
56 |
57 | if (!(bucket.GetValueKind() == JsonValueKind.Array))
58 | {
59 | continue;
60 | }
61 |
62 | var bucketArr = bucket.AsArray();
63 |
64 | if (!bucketArr.Any())
65 | {
66 | continue;
67 | }
68 |
69 | var weight = 1;
70 |
71 | if (bucketArr.Count >= 2 && bucketArr.ElementAt(1).GetValueKind() == JsonValueKind.Number)
72 | {
73 | weight = bucketArr.ElementAt(1).GetValue();
74 | }
75 |
76 | distributions.Add(new FractionalEvaluationDistribution
77 | {
78 | variant = bucketArr.ElementAt(0).ToString(),
79 | weight = weight
80 | });
81 |
82 | distributionSum += weight;
83 | }
84 |
85 | var valueToDistribute = propertyValue;
86 | var murmur32 = MurmurHash.Create32();
87 | var bytes = Encoding.ASCII.GetBytes(valueToDistribute);
88 | var hashBytes = murmur32.ComputeHash(bytes);
89 | var hash = BitConverter.ToInt32(hashBytes, 0);
90 |
91 | var bucketValue = (int)(Math.Abs((float)hash) / Int32.MaxValue * 100);
92 |
93 | var rangeEnd = 0.0;
94 |
95 | foreach (var dist in distributions)
96 | {
97 | rangeEnd += 100 * (dist.weight / (float)distributionSum);
98 | if (bucketValue < rangeEnd)
99 | {
100 | return dist.variant;
101 | }
102 | }
103 |
104 | return "";
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/CustomEvaluators/SemVerRule.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Nodes;
2 | using Json.Logic;
3 | using Semver;
4 |
5 | namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess.CustomEvaluators;
6 |
7 | ///
8 | internal sealed class SemVerRule : IRule
9 | {
10 | const string OperatorEqual = "=";
11 | const string OperatorNotEqual = "!=";
12 | const string OperatorLess = "<";
13 | const string OperatorLessOrEqual = "<=";
14 | const string OperatorGreater = ">";
15 | const string OperatorGreaterOrEqual = ">=";
16 | const string OperatorMatchMajor = "^";
17 | const string OperatorMatchMinor = "~";
18 |
19 |
20 | ///
21 | public JsonNode Apply(JsonNode args, EvaluationContext context)
22 | {
23 | // check if we have at least 3 arguments
24 | if (args.AsArray().Count < 3)
25 | {
26 | return false;
27 | }
28 | // get the value from the provided evaluation context
29 | var versionString = JsonLogic.Apply(args[0], context).ToString();
30 |
31 | // get the operator
32 | var semVerOperator = JsonLogic.Apply(args[1], context).ToString();
33 |
34 | // get the target version
35 | var targetVersionString = JsonLogic.Apply(args[2], context).ToString();
36 |
37 | //convert to semantic versions
38 | if (!SemVersion.TryParse(versionString, SemVersionStyles.Strict, out var version) ||
39 | !SemVersion.TryParse(targetVersionString, SemVersionStyles.Strict, out var targetVersion))
40 | {
41 | return false;
42 | }
43 |
44 | switch (semVerOperator)
45 | {
46 | case OperatorEqual:
47 | return version.CompareSortOrderTo(targetVersion) == 0;
48 | case OperatorNotEqual:
49 | return version.CompareSortOrderTo(targetVersion) != 0;
50 | case OperatorLess:
51 | return version.CompareSortOrderTo(targetVersion) < 0;
52 | case OperatorLessOrEqual:
53 | return version.CompareSortOrderTo(targetVersion) <= 0;
54 | case OperatorGreater:
55 | return version.CompareSortOrderTo(targetVersion) > 0;
56 | case OperatorGreaterOrEqual:
57 | return version.CompareSortOrderTo(targetVersion) >= 0;
58 | case OperatorMatchMajor:
59 | return version.Major == targetVersion.Major;
60 | case OperatorMatchMinor:
61 | return version.Major == targetVersion.Major && version.Minor == targetVersion.Minor;
62 | default:
63 | return false;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/CustomEvaluators/StringRule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json;
3 | using System.Text.Json.Nodes;
4 | using Json.Logic;
5 |
6 | namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess.CustomEvaluators;
7 |
8 | internal sealed class StartsWithRule : IRule
9 | {
10 | public JsonNode Apply(JsonNode args, EvaluationContext context)
11 | {
12 | if (!StringRule.isValid(args, context, out string operandA, out string operandB))
13 | {
14 | return false;
15 | }
16 | return Convert.ToString(operandA).StartsWith(Convert.ToString(operandB));
17 | }
18 | }
19 |
20 | internal sealed class EndsWithRule : IRule
21 | {
22 | public JsonNode Apply(JsonNode args, EvaluationContext context)
23 | {
24 | if (!StringRule.isValid(args, context, out string operandA, out string operandB))
25 | {
26 | return false;
27 | }
28 | return operandA.EndsWith(operandB);
29 | }
30 | }
31 |
32 | internal static class StringRule
33 | {
34 | internal static bool isValid(JsonNode args, EvaluationContext context, out string argA, out string argB)
35 | {
36 | argA = null;
37 | argB = null;
38 |
39 | // check if we have at least 2 arguments
40 | if (args.AsArray().Count < 2)
41 | {
42 | return false;
43 | }
44 |
45 | var nodeA = JsonLogic.Apply(args[0], context);
46 | var nodeB = JsonLogic.Apply(args[1], context);
47 |
48 | // return false immediately if both operands are not strings
49 | if (nodeA?.GetValueKind() != JsonValueKind.String || nodeB?.GetValueKind() != JsonValueKind.String)
50 | {
51 | return false;
52 | }
53 |
54 | argA = nodeA.ToString();
55 | argB = nodeB.ToString();
56 | return true;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/JsonSchemaValidator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microsoft.Extensions.Logging;
6 | using NJsonSchema;
7 | using NJsonSchema.Generation;
8 |
9 | namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess;
10 |
11 | internal interface IJsonSchemaValidator
12 | {
13 | Task InitializeAsync(CancellationToken cancellationToken = default);
14 | void Validate(string configuration);
15 | }
16 |
17 | internal class JsonSchemaValidator : IJsonSchemaValidator
18 | {
19 | private readonly HttpClient _client;
20 | private readonly ILogger _logger;
21 | private JsonSchema _validator;
22 |
23 | internal JsonSchemaValidator(HttpClient client, ILogger logger)
24 | {
25 | if (client == null)
26 | {
27 | client = new HttpClient
28 | {
29 | BaseAddress = new Uri("https://flagd.dev"),
30 | };
31 | }
32 |
33 | _client = client;
34 | _logger = logger;
35 | }
36 |
37 | public async Task InitializeAsync(CancellationToken cancellationToken = default)
38 | {
39 | try
40 | {
41 | var targetingTask = _client.GetAsync("/schema/v0/targeting.json", cancellationToken);
42 | var flagTask = _client.GetAsync("/schema/v0/flags.json", cancellationToken);
43 |
44 | await Task.WhenAll(targetingTask, flagTask).ConfigureAwait(false);
45 |
46 | var targeting = targetingTask.Result;
47 | var flag = flagTask.Result;
48 |
49 | if (!targeting.IsSuccessStatusCode)
50 | {
51 | _logger.LogWarning("Unable to retrieve Flagd targeting JSON Schema, status code: {StatusCode}", targeting.StatusCode);
52 | return;
53 | }
54 |
55 | if (!flag.IsSuccessStatusCode)
56 | {
57 | _logger.LogWarning("Unable to retrieve Flagd flags JSON Schema, status code: {StatusCode}", flag.StatusCode);
58 | return;
59 | }
60 |
61 | #if NET5_0_OR_GREATER
62 | var targetingJson = await targeting.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
63 | #else
64 | var targetingJson = await targeting.Content.ReadAsStringAsync().ConfigureAwait(false);
65 | #endif
66 |
67 | var targetingSchema = await JsonSchema.FromJsonAsync(targetingJson, "targeting.json", schema =>
68 | {
69 | var schemaResolver = new JsonSchemaResolver(schema, new SystemTextJsonSchemaGeneratorSettings());
70 | var resolver = new JsonReferenceResolver(schemaResolver);
71 |
72 | return resolver;
73 | }, cancellationToken).ConfigureAwait(false);
74 |
75 | #if NET5_0_OR_GREATER
76 | var flagJson = await flag.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
77 | #else
78 | var flagJson = await flag.Content.ReadAsStringAsync().ConfigureAwait(false);
79 | #endif
80 | var flagSchema = await JsonSchema.FromJsonAsync(flagJson, "flags.json", schema =>
81 | {
82 | var schemaResolver = new JsonSchemaResolver(schema, new SystemTextJsonSchemaGeneratorSettings());
83 | var resolver = new JsonReferenceResolver(schemaResolver);
84 |
85 | resolver.AddDocumentReference("targeting.json", targetingSchema);
86 | return resolver;
87 | }, cancellationToken).ConfigureAwait(false);
88 |
89 | _validator = flagSchema;
90 | }
91 | catch (Exception ex)
92 | {
93 | _logger.LogError(ex, "Unable to retrieve Flagd flags and targeting JSON Schemas");
94 | }
95 | }
96 |
97 | public void Validate(string configuration)
98 | {
99 | if (_validator != null)
100 | {
101 | var errors = _validator.Validate(configuration);
102 | if (errors.Count > 0)
103 | {
104 | _logger.LogWarning("Validating Flagd configuration resulted in Schema Validation errors {Errors}",
105 | errors);
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/Resolver/Resolver.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using OpenFeature.Model;
3 | using Value = OpenFeature.Model.Value;
4 |
5 | namespace OpenFeature.Contrib.Providers.Flagd.Resolver;
6 |
7 | internal interface Resolver
8 | {
9 | Task Init();
10 | Task Shutdown();
11 |
12 | Task> ResolveBooleanValueAsync(string flagKey, bool defaultValue,
13 | EvaluationContext context = null);
14 |
15 | Task> ResolveStringValueAsync(string flagKey, string defaultValue,
16 | EvaluationContext context = null);
17 |
18 | Task> ResolveIntegerValueAsync(string flagKey, int defaultValue,
19 | EvaluationContext context = null);
20 |
21 | Task> ResolveDoubleValueAsync(string flagKey, double defaultValue,
22 | EvaluationContext context = null);
23 |
24 | Task> ResolveStructureValueAsync(string flagKey, Value defaultValue,
25 | EvaluationContext context = null);
26 | }
27 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/UnixDomainSocketConnectionFactory.cs:
--------------------------------------------------------------------------------
1 | #if NET5_0_OR_GREATER
2 | using System;
3 | using System.IO;
4 | using System.Net;
5 | using System.Net.Http;
6 | using System.Net.Sockets;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace OpenFeature.Contrib.Providers.Flagd;
11 |
12 | ///
13 | public class UnixDomainSocketConnectionFactory
14 | {
15 | private readonly EndPoint _endPoint;
16 |
17 | ///
18 | /// Constructor of the connection factory
19 | /// The path to the unix socket
20 | ///
21 | public UnixDomainSocketConnectionFactory(EndPoint endpoint)
22 | {
23 | _endPoint = endpoint;
24 | }
25 |
26 | #if NET5_0_OR_GREATER
27 | ///
28 | /// ConnectAsync is a custom implementation of the ConnectAsync method used by the grpc client
29 | ///
30 | /// unused - SocketsHttpConnectionContext
31 | /// The cancellation token
32 | /// A ValueTask object representing the given
33 | public async ValueTask ConnectAsync(SocketsHttpConnectionContext _, CancellationToken cancellationToken = default)
34 | {
35 | var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
36 |
37 | try
38 | {
39 | await socket.ConnectAsync(_endPoint, cancellationToken).ConfigureAwait(false);
40 | return new NetworkStream(socket, true);
41 | }
42 | catch (Exception ex)
43 | {
44 | socket.Dispose();
45 | throw new HttpRequestException($"Error connecting to '{_endPoint}'.", ex);
46 | }
47 | }
48 | #endif
49 | }
50 | #endif
51 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/Utils/CertificateLoader.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Security.Cryptography.X509Certificates;
3 |
4 | namespace OpenFeature.Contrib.Providers.Flagd.Utils;
5 |
6 | internal static class CertificateLoader
7 | {
8 | internal static X509Certificate2 LoadCertificate(string certificatePath)
9 | {
10 | if (string.IsNullOrWhiteSpace(certificatePath))
11 | {
12 | return null;
13 | }
14 |
15 | if (!File.Exists(certificatePath))
16 | {
17 | throw new FileNotFoundException($"Certificate file not found: {certificatePath}");
18 | }
19 |
20 | #if NET9_0_OR_GREATER
21 | return X509CertificateLoader.LoadCertificateFromFile(certificatePath);
22 | #else
23 | return new X509Certificate2(certificatePath);
24 | #endif
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagd/version.txt:
--------------------------------------------------------------------------------
1 | 0.3.2
2 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagsmith/FlagsmithProviderConfiguration.cs:
--------------------------------------------------------------------------------
1 | namespace OpenFeature.Contrib.Providers.Flagsmith;
2 |
3 | ///
4 | /// Settings for Flagsmith open feature provider
5 | ///
6 | public class FlagsmithProviderConfiguration : IFlagsmithProviderConfiguration
7 | {
8 | ///
9 | public bool UsingBooleanConfigValue { get; set; }
10 | }
11 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagsmith/IFlagsmithProviderConfiguration.cs:
--------------------------------------------------------------------------------
1 | namespace OpenFeature.Contrib.Providers.Flagsmith;
2 |
3 | ///
4 | /// Settings for Flagsmith Open feature provider
5 | ///
6 | public interface IFlagsmithProviderConfiguration
7 | {
8 | ///
9 | /// Determines whether to resolve a feature value as a boolean or use
10 | /// the isFeatureEnabled as the flag itself. These values will be false
11 | /// and true respectively.
12 | /// Default: false
13 | ///
14 | public bool UsingBooleanConfigValue { get; }
15 | }
16 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagsmith/OpenFeature.Contrib.Providers.Flagsmith.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OpenFeature.Contrib.Providers.Flagsmith
5 | 0.2.1
6 | $(VersionNumber)
7 | $(VersionNumber)
8 | $(VersionNumber)
9 | Flagsmith provider for .NET
10 | Vladimir Petrusevici
11 | true
12 |
13 |
14 |
15 |
16 |
17 | <_Parameter1>$(MSBuildProjectName).Test
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagsmith/README.md:
--------------------------------------------------------------------------------
1 | # Flagsmith .NET Provider
2 |
3 | The Flagsmith provider allows you to connect to your Flagsmith instance through the OpenFeature SDK
4 |
5 | # .Net SDK usage
6 |
7 | ## Requirements
8 |
9 | - open-feature/dotnet-sdk v1.5.0 > v2.0.0
10 |
11 | ## Install dependencies
12 |
13 | The first things we will do is install the **Open Feature SDK** and the **Flagsmith Feature Flag provider**.
14 |
15 | ### .NET Cli
16 | ```shell
17 | dotnet add package OpenFeature.Contrib.Providers.Flagsmith
18 | ```
19 | ### Package Manager
20 |
21 | ```shell
22 | NuGet\Install-Package OpenFeature.Contrib.Providers.Flagsmith
23 | ```
24 | ### Package Reference
25 |
26 | ```xml
27 |
28 | ```
29 | ### Packet cli
30 |
31 | ```shell
32 | packet add OpenFeature.Contrib.Providers.Flagsmith
33 | ```
34 |
35 | ### Cake
36 |
37 | ```shell
38 | // Install OpenFeature.Contrib.Providers.Flagsmith as a Cake Addin
39 | #addin nuget:?package=OpenFeature.Contrib.Providers.Flagsmith
40 |
41 | // Install OpenFeature.Contrib.Providers.Flagsmith as a Cake Tool
42 | #tool nuget:?package=OpenFeature.Contrib.Providers.Flagsmith
43 | ```
44 |
45 | ## Using the Flagsmith Provider with the OpenFeature SDK
46 |
47 | To create a Flagmith provider you should define provider and Flagsmith settings.
48 |
49 | ```csharp
50 | using Flagsmith;
51 | using OpenFeature.Contrib.Providers.Flagsmith;
52 | using OpenFeature.Model;
53 |
54 | // Additional configs for provider
55 | var providerConfig = new FlagsmithProviderConfiguration();
56 |
57 | // Flagsmith client configuration
58 | var flagsmithConfig = new FlagsmithConfiguration
59 | {
60 | ApiUrl = "https://edge.api.flagsmith.com/api/v1/",
61 | EnvironmentKey = "",
62 | EnableClientSideEvaluation = false,
63 | EnvironmentRefreshIntervalSeconds = 60,
64 | EnableAnalytics = false,
65 | Retries = 1,
66 | };
67 | var flagsmithProvider = new FlagsmithProvider(providerConfig, flagsmithConfig);
68 |
69 | // Set the flagsmithProvider as the provider for the OpenFeature SDK
70 | await OpenFeature.Api.Instance.SetProviderAsync(flagsmithProvider);
71 |
72 | // Get an OpenFeature client
73 | var client = OpenFeature.Api.Instance.GetClient("my-app");
74 |
75 | // Optional: set a targeting key and traits to use segment and/or identity overrides
76 | var context = EvaluationContext.Builder()
77 | .SetTargetingKey("my-flagsmith-identity-ID")
78 | .Set("my-trait-key", "my-trait-value")
79 | .Build();
80 |
81 | // Evaluate a flag
82 | var val = await client.GetBooleanValueAsync("myBoolFlag", false, context);
83 |
84 | // Print the value of the 'myBoolFlag' feature flag
85 | Console.WriteLine(val);
86 | ```
87 |
88 | You also can create Flagsmith provider using ```HttpClient``` or precreated ```FlagsmithClient```
89 |
90 | ```csharp
91 | using var httpClient = new HttpClient();
92 | var flagsmithProvider = new FlagsmithProvider(providerConfig, config, httpClient);
93 | ```
94 | ```csharp
95 | using var flagsmithClient = new FlagsmithClient(flagsmithOptions);
96 | var flagsmithProvider = new FlagsmithProvider(providerConfig, flagsmithClient);
97 | ```
98 | ### Configuring the FlagsmithProvider
99 |
100 | To configure FlagsmithConfiguration just use [an example](https://github.com/Flagsmith/flagsmith-dotnet-client/tree/main/Example) from Flagsmith GitHub.
101 | For FlagsmithProviderConfiguration you can configure next parameters using custom implementation or just ```FlagsmithProviderConfiguration```:
102 | ```csharp
103 | public interface IFlagsmithProviderConfiguration
104 | {
105 | ///
106 | /// Key that will be used as identity for Flagsmith requests.
107 | ///
108 | public string TargetingKey { get; }
109 |
110 | ///
111 | /// Determines whether to resolve a feature value as a boolean or use
112 | /// the isFeatureEnabled as the flag itself. These values will be false
113 | /// and true respectively.
114 | /// Default: false
115 | ///
116 | public bool UsingBooleanConfigValue { get; }
117 | }
118 | ```
119 |
120 |
121 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flagsmith/version.txt:
--------------------------------------------------------------------------------
1 | 0.2.1
2 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.0.5](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.Flipt-v0.0.4...OpenFeature.Contrib.Providers.Flipt-v0.0.5) (2024-10-18)
4 |
5 |
6 | ### 🐛 Bug Fixes
7 |
8 | * update readme ([1aaa387](https://github.com/open-feature/dotnet-sdk-contrib/commit/1aaa3877ae3db884d401226b2138f8e3903a56c2))
9 |
10 | ## [0.0.4](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.Flipt-v0.0.3...OpenFeature.Contrib.Providers.Flipt-v0.0.4) (2024-10-18)
11 |
12 |
13 | ### 🐛 Bug Fixes
14 |
15 | * update docs ([#300](https://github.com/open-feature/dotnet-sdk-contrib/issues/300)) ([50fd738](https://github.com/open-feature/dotnet-sdk-contrib/commit/50fd738585567a39f6fd0b1db37b899cbae42ba5))
16 |
17 | ## [0.0.3](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.Flipt-v0.0.2...OpenFeature.Contrib.Providers.Flipt-v0.0.3) (2024-10-18)
18 |
19 |
20 | ### 🐛 Bug Fixes
21 |
22 | * force a republish ([#298](https://github.com/open-feature/dotnet-sdk-contrib/issues/298)) ([ad01db2](https://github.com/open-feature/dotnet-sdk-contrib/commit/ad01db2991a147d527637afac30827f73a4cc40e))
23 |
24 | ## [0.0.2](https://github.com/open-feature/dotnet-sdk-contrib/compare/OpenFeature.Contrib.Providers.Flipt-v0.0.1...OpenFeature.Contrib.Providers.Flipt-v0.0.2) (2024-10-17)
25 |
26 |
27 | ### ✨ New Features
28 |
29 | * Introduce flipt provider for dotnet ([#293](https://github.com/open-feature/dotnet-sdk-contrib/issues/293)) ([4d59bc3](https://github.com/open-feature/dotnet-sdk-contrib/commit/4d59bc35bd4c65c9989e8c980668d85242240eec))
30 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/ClientWrapper/FliptClientWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Flipt.Rest;
5 |
6 | namespace OpenFeature.Contrib.Providers.Flipt.ClientWrapper;
7 |
8 | ///
9 | /// Wrapper for Flipt server sdk client for .net
10 | ///
11 | public class FliptClientWrapper : IFliptClientWrapper
12 | {
13 | private readonly FliptRestClient _fliptRestClient;
14 |
15 | ///
16 | ///
17 | /// Url of flipt instance
18 | /// Authentication access token
19 | /// Timeout when calling flipt endpoints in seconds
20 | public FliptClientWrapper(string fliptUrl,
21 | string clientToken = "",
22 | int timeoutInSeconds = 30)
23 | {
24 | _fliptRestClient = BuildClient(fliptUrl, clientToken, timeoutInSeconds);
25 | }
26 |
27 | ///
28 | public async Task EvaluateVariantAsync(EvaluationRequest evaluationRequest)
29 | {
30 | return await _fliptRestClient.EvaluateV1VariantAsync(evaluationRequest).ConfigureAwait(false);
31 | }
32 |
33 | ///
34 | public async Task EvaluateBooleanAsync(EvaluationRequest evaluationRequest)
35 | {
36 | return await _fliptRestClient.EvaluateV1BooleanAsync(evaluationRequest).ConfigureAwait(false);
37 | }
38 |
39 | private static FliptRestClient BuildClient(string fliptUrl, string clientToken, int timeoutInSeconds = 30)
40 | {
41 | var httpClient = new HttpClient
42 | {
43 | BaseAddress = new Uri(fliptUrl),
44 | Timeout = TimeSpan.FromSeconds(timeoutInSeconds),
45 | DefaultRequestHeaders = { { "Authorization", $"Bearer {clientToken}" } }
46 | };
47 | return new FliptRestClient(httpClient);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/ClientWrapper/IFliptClientWrapper.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Flipt.Rest;
3 |
4 | namespace OpenFeature.Contrib.Providers.Flipt.ClientWrapper;
5 |
6 | ///
7 | ///
8 | public interface IFliptClientWrapper
9 | {
10 | ///
11 | /// Wrapper to Flipt.io/EvaluateVariantAsync method
12 | ///
13 | ///
14 | ///
15 | Task EvaluateVariantAsync(EvaluationRequest evaluationRequest);
16 |
17 | ///
18 | /// Wrapper to Flipt.io/EvaluateBooleanAsync method
19 | ///
20 | ///
21 | ///
22 | Task EvaluateBooleanAsync(EvaluationRequest evaluationRequest);
23 | }
24 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/Converters/JsonConverterExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 |
3 | namespace OpenFeature.Contrib.Providers.Flipt.Converters;
4 |
5 | ///
6 | /// Extensions for default JsonConverter behavior
7 | ///
8 | public static class JsonConverterExtensions
9 | {
10 | ///
11 | /// JsonConverter serializer settings for Flipt to OpenFeature model deserialization
12 | ///
13 | public static readonly JsonSerializerOptions DefaultSerializerSettings = new()
14 | {
15 | WriteIndented = true,
16 | AllowTrailingCommas = true,
17 | Converters =
18 | {
19 | new OpenFeatureStructureConverter(),
20 | new OpenFeatureValueConverter()
21 | }
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/Converters/OpenFeatureStructureConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using OpenFeature.Model;
6 |
7 | namespace OpenFeature.Contrib.Providers.Flipt.Converters;
8 |
9 | ///
10 | /// JsonConverter for OpenFeature Structure type
11 | ///
12 | public class OpenFeatureStructureConverter : JsonConverter
13 | {
14 | ///
15 | public override void Write(Utf8JsonWriter writer, Structure value, JsonSerializerOptions options)
16 | {
17 | var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(value.AsDictionary(),
18 | JsonConverterExtensions.DefaultSerializerSettings));
19 | jsonDoc.WriteTo(writer);
20 | }
21 |
22 | ///
23 | public override Structure Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
24 | {
25 | using var jsonDocument = JsonDocument.ParseValue(ref reader);
26 | var jsonText = jsonDocument.RootElement.GetRawText();
27 | return new Structure(JsonSerializer.Deserialize>(jsonText,
28 | JsonConverterExtensions.DefaultSerializerSettings));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/Converters/OpenFeatureValueConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using OpenFeature.Model;
6 |
7 | namespace OpenFeature.Contrib.Providers.Flipt.Converters;
8 |
9 | ///
10 | /// OpenFeature Value type converter
11 | ///
12 | public class OpenFeatureValueConverter : JsonConverter
13 | {
14 | ///
15 | public override Value Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
16 | {
17 | var value = new Value();
18 | switch (reader.TokenType)
19 | {
20 | case JsonTokenType.String:
21 | return reader.TryGetDateTime(out var dateTimeValue)
22 | ? new Value(dateTimeValue)
23 | : new Value(reader.GetString() ?? string.Empty);
24 | case JsonTokenType.True:
25 | case JsonTokenType.False:
26 | return new Value(reader.GetBoolean());
27 | case JsonTokenType.Number:
28 | if (reader.TryGetInt32(out var intValue)) return new Value(intValue);
29 | if (reader.TryGetDouble(out var dblValue)) return new Value(dblValue);
30 | break;
31 | case JsonTokenType.StartArray:
32 | return new Value(GenerateValueArray(ref reader, typeToConvert, options));
33 | case JsonTokenType.StartObject:
34 | return new Value(GetStructure(ref reader, typeToConvert, options));
35 | }
36 |
37 | return value;
38 | }
39 |
40 | private Structure GetStructure(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
41 | {
42 | var startDepth = reader.CurrentDepth;
43 | var structureDictionary = new Dictionary();
44 | while (reader.Read())
45 | {
46 | if (reader.TokenType == JsonTokenType.PropertyName)
47 | {
48 | var key = reader.GetString();
49 | reader.Read();
50 | var val = Read(ref reader, typeToConvert, options);
51 | structureDictionary[key ?? string.Empty] = val;
52 | }
53 |
54 | if (reader.TokenType == JsonTokenType.EndObject && reader.CurrentDepth == startDepth) break;
55 | }
56 |
57 | return new Structure(structureDictionary);
58 | }
59 |
60 |
61 | private IList GenerateValueArray(ref Utf8JsonReader reader, Type typeToConvert,
62 | JsonSerializerOptions options)
63 | {
64 | var valuesArray = new List();
65 | var startDepth = reader.CurrentDepth;
66 |
67 | while (reader.Read())
68 | switch (reader.TokenType)
69 | {
70 | case JsonTokenType.EndArray when reader.CurrentDepth == startDepth:
71 | return valuesArray;
72 | default:
73 | valuesArray.Add(Read(ref reader, typeToConvert, options));
74 | break;
75 | }
76 |
77 | return valuesArray;
78 | }
79 |
80 | ///
81 | public override void Write(Utf8JsonWriter writer, Value value, JsonSerializerOptions options)
82 | {
83 | if (value.IsList)
84 | {
85 | writer.WriteStartArray();
86 | foreach (var val in value.AsList!)
87 | {
88 | var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(val.AsObject,
89 | JsonConverterExtensions.DefaultSerializerSettings));
90 | jsonDoc.WriteTo(writer);
91 | }
92 |
93 | writer.WriteEndArray();
94 | }
95 | else
96 | {
97 | var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(value.AsObject,
98 | JsonConverterExtensions.DefaultSerializerSettings));
99 | jsonDoc.WriteTo(writer);
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/FliptExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text.Json;
4 | using OpenFeature.Contrib.Providers.Flipt.Converters;
5 | using OpenFeature.Model;
6 |
7 | namespace OpenFeature.Contrib.Providers.Flipt;
8 |
9 | ///
10 | /// Extension helper methods
11 | ///
12 | public static class FliptExtensions
13 | {
14 | ///
15 | /// Transforms openFeature EvaluationContext to a mutable Dictionary that flipt sdk accepts
16 | ///
17 | /// OpenFeature EvaluationContext
18 | ///
19 | public static Dictionary ToStringDictionary(this EvaluationContext evaluationContext)
20 | {
21 | return evaluationContext?.AsDictionary()
22 | .ToDictionary(k => k.Key,
23 | v => JsonSerializer.Serialize(v.Value.AsObject,
24 | JsonConverterExtensions.DefaultSerializerSettings)) ??
25 | [];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/FliptProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using OpenFeature.Model;
4 |
5 | namespace OpenFeature.Contrib.Providers.Flipt;
6 |
7 | ///
8 | /// FliptProvider is the .NET provider implementation for Flipt.io
9 | ///
10 | ///
11 | /// Accepts an instantiated IFliptClientWrapper instance
12 | ///
13 | public class FliptProvider : FeatureProvider
14 | {
15 | private static readonly Metadata Metadata = new("Flipt Provider");
16 | private readonly IFliptToOpenFeatureConverter _fliptToOpenFeatureConverter;
17 |
18 | ///
19 | /// Instantiate a FliptProvider using configuration params
20 | ///
21 | /// Url of flipt instance
22 | /// Namespace used for querying flags
23 | /// Authentication access token
24 | /// Timeout when calling flipt endpoints in seconds
25 | public FliptProvider(string fliptUrl, string namespaceKey = "default", string clientToken = "",
26 | int timeoutInSeconds = 30) : this(new FliptToOpenFeatureConverter(fliptUrl, namespaceKey, clientToken,
27 | timeoutInSeconds))
28 | {
29 | }
30 |
31 | internal FliptProvider(IFliptToOpenFeatureConverter fliptToOpenFeatureConverter)
32 | {
33 | _fliptToOpenFeatureConverter = fliptToOpenFeatureConverter;
34 | }
35 |
36 | ///
37 | public override Metadata GetMetadata()
38 | {
39 | return Metadata;
40 | }
41 |
42 | ///
43 | public override async Task> ResolveBooleanValueAsync(string flagKey, bool defaultValue,
44 | EvaluationContext context = null,
45 | CancellationToken cancellationToken = new())
46 | {
47 | return await _fliptToOpenFeatureConverter.EvaluateBooleanAsync(flagKey, defaultValue, context).ConfigureAwait(false);
48 | }
49 |
50 | ///
51 | public override async Task> ResolveStringValueAsync(string flagKey,
52 | string defaultValue, EvaluationContext context = null,
53 | CancellationToken cancellationToken = new())
54 | {
55 | return await _fliptToOpenFeatureConverter.EvaluateAsync(flagKey, defaultValue, context).ConfigureAwait(false);
56 | }
57 |
58 | ///
59 | public override async Task> ResolveIntegerValueAsync(string flagKey, int defaultValue,
60 | EvaluationContext context = null,
61 | CancellationToken cancellationToken = new())
62 | {
63 | return await _fliptToOpenFeatureConverter.EvaluateAsync(flagKey, defaultValue, context).ConfigureAwait(false);
64 | }
65 |
66 | ///
67 | public override async Task> ResolveDoubleValueAsync(string flagKey, double defaultValue,
68 | EvaluationContext context = null,
69 | CancellationToken cancellationToken = new())
70 | {
71 | return await _fliptToOpenFeatureConverter.EvaluateAsync(flagKey, defaultValue, context).ConfigureAwait(false);
72 | }
73 |
74 | ///
75 | public override async Task> ResolveStructureValueAsync(string flagKey, Value defaultValue,
76 | EvaluationContext context = null,
77 | CancellationToken cancellationToken = new())
78 | {
79 | return await _fliptToOpenFeatureConverter.EvaluateAsync(flagKey, defaultValue, context).ConfigureAwait(false);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/OpenFeature.Contrib.Providers.Flipt.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OpenFeature.Contrib.Providers.Flipt
5 | 0.0.5
6 | $(VersionNumber)
7 | $(VersionNumber)
8 | $(VersionNumber)
9 | Flipt provider for .NET
10 | Jean Andrei de la Cruz Austria
11 | true
12 |
13 |
14 |
15 |
16 |
17 | <_Parameter1>$(MSBuildProjectName).Test
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | all
29 | runtime; build; native; contentfiles; analyzers; buildtransitive
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.Flipt/version.txt:
--------------------------------------------------------------------------------
1 | 0.0.5
2 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.GOFeatureFlag/GoFeatureFlagProviderOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using OpenFeature.Contrib.Providers.GOFeatureFlag.models;
4 |
5 | namespace OpenFeature.Contrib.Providers.GOFeatureFlag;
6 |
7 | ///
8 | /// GoFeatureFlagProviderOptions contains the options to initialise the provider.
9 | ///
10 | public class GoFeatureFlagProviderOptions
11 | {
12 | ///
13 | /// (mandatory) endpoint contains the DNS of your GO Feature Flag relay proxy
14 | /// example: https://mydomain.com/gofeatureflagproxy/
15 | ///
16 | public string Endpoint { get; set; }
17 |
18 | ///
19 | /// (optional) timeout we are waiting when calling the go-feature-flag relay proxy API.
20 | /// Default: 10000 ms
21 | ///
22 | public TimeSpan Timeout { get; set; } = new TimeSpan(10000 * TimeSpan.TicksPerMillisecond);
23 |
24 | ///
25 | /// (optional) If you want to provide your own HttpMessageHandler.
26 | /// Default: null
27 | ///
28 | public HttpMessageHandler HttpMessageHandler { get; set; }
29 |
30 | ///
31 | /// (optional) If the relay proxy is configured to authenticate the request, you should provide
32 | /// an API Key to the provider.
33 | /// Please ask the administrator of the relay proxy to provide an API Key.
34 | /// (This feature is available only if you are using GO Feature Flag relay proxy v1.7.0 or above)
35 | /// Default: null
36 | ///
37 | public string ApiKey { get; set; }
38 |
39 | ///
40 | /// (optional) ExporterMetadata are static information you can set that will be available in the
41 | /// evaluation data sent to the exporter.
42 | ///
43 | public ExporterMetadata ExporterMetadata { get; set; }
44 | }
45 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.GOFeatureFlag/OpenFeature.Contrib.Providers.GOFeatureFlag.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OpenFeature.Contrib.GOFeatureFlag
5 | 0.2.1
6 | $(VersionNumber)
7 | $(VersionNumber)
8 | $(VersionNumber)
9 | GO Feature Flag provider for .NET
10 | Thomas Poignant
11 | true
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.GOFeatureFlag/README.md:
--------------------------------------------------------------------------------
1 | # GO Feature Flag .NET Provider
2 |
3 | GO Feature Flag provider allows you to connect to your GO Feature Flag instance.
4 |
5 | [GO Feature Flag](https://gofeatureflag.org) believes in simplicity and offers a simple and lightweight solution to use feature flags.
6 | Our focus is to avoid any complex infrastructure work to use GO Feature Flag.
7 |
8 | This is a complete feature flagging solution with the possibility to target only a group of users, use any types of flags, store your configuration in various location and advanced rollout functionality. You can also collect usage data of your flags and be notified of configuration changes.
9 |
10 | # .Net SDK usage
11 |
12 | ## Requirements
13 |
14 | - open-feature/dotnet-sdk v1.5.0 > v2.0.0
15 |
16 | ## Install dependencies
17 |
18 | The first things we will do is install the **Open Feature SDK** and the **GO Feature Flag provider**.
19 |
20 | ### .NET Cli
21 | ```shell
22 | dotnet add package OpenFeature.Contrib.GOFeatureFlag
23 | ```
24 | ### Package Manager
25 |
26 | ```shell
27 | NuGet\Install-Package OpenFeature.Contrib.GOFeatureFlag
28 | ```
29 | ### Package Reference
30 |
31 | ```xml
32 |
33 | ```
34 | ### Packet cli
35 |
36 | ```shell
37 | paket add OpenFeature.Contrib.GOFeatureFlag
38 | ```
39 |
40 | ### Cake
41 |
42 | ```shell
43 | // Install OpenFeature.Contrib.GOFeatureFlag as a Cake Addin
44 | #addin nuget:?package=OpenFeature.Contrib.GOFeatureFlag
45 |
46 | // Install OpenFeature.Contrib.GOFeatureFlag as a Cake Tool
47 | #tool nuget:?package=OpenFeature.Contrib.GOFeatureFlag
48 | ```
49 |
50 | ## Initialize your Open Feature client
51 |
52 | To evaluate the flags you need to have an Open Feature configured in you app.
53 | This code block shows you how you can create a client that you can use in your application.
54 |
55 | ```csharp
56 | using OpenFeature;
57 | using OpenFeature.Contrib.Providers.GOFeatureFlag;
58 |
59 | // ...
60 |
61 | var goFeatureFlagProvider = new GoFeatureFlagProvider(new GoFeatureFlagProviderOptions
62 | {
63 | Endpoint = "http://localhost:1031/",
64 | Timeout = new TimeSpan(1000 * TimeSpan.TicksPerMillisecond)
65 | });
66 | Api.Instance.SetProvider(goFeatureFlagProvider);
67 | var client = Api.Instance.GetClient("my-app");
68 | ```
69 |
70 | ## Evaluate your flag
71 |
72 | This code block explain how you can create an `EvaluationContext` and use it to evaluate your flag.
73 |
74 |
75 | > In this example we are evaluating a `boolean` flag, but other types are available.
76 | >
77 | > **Refer to the [Open Feature documentation](https://openfeature.dev/docs/reference/concepts/evaluation-api#basic-evaluation) to know more about it.**
78 |
79 | ```csharp
80 | // Context of your flag evaluation.
81 | // With GO Feature Flag you MUST have a targetingKey that is a unique identifier of the user.
82 | var userContext = EvaluationContext.Builder()
83 | .Set("targetingKey", "1d1b9238-2591-4a47-94cf-d2bc080892f1") // user unique identifier (mandatory)
84 | .Set("firstname", "john")
85 | .Set("lastname", "doe")
86 | .Set("email", "john.doe@gofeatureflag.org")
87 | .Set("admin", true) // this field is used in the targeting rule of the flag "flag-only-for-admin"
88 | .Set("anonymous", false)
89 | .Build();
90 |
91 | var adminFlag = await client.GetBooleanValueAsync("flag-only-for-admin", false, userContext);
92 | if (adminFlag) {
93 | // flag "flag-only-for-admin" is true for the user
94 | } else {
95 | // flag "flag-only-for-admin" is false for the user
96 | }
97 | ```
98 |
--------------------------------------------------------------------------------
/src/OpenFeature.Contrib.Providers.GOFeatureFlag/converters/DictionaryConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text.Json;
4 |
5 | namespace OpenFeature.Contrib.Providers.GOFeatureFlag.converters;
6 |
7 | ///
8 | /// DictionaryConverter is converting a json Dictionary to a Dictionary with real object.
9 | ///
10 | public static class DictionaryConverter
11 | {
12 | ///
13 | /// Function that convert the dictionary to a Dictionary with real object.
14 | ///
15 | ///
16 | /// A dictionary with real types.
17 | public static Dictionary ConvertDictionary(Dictionary inputDictionary)
18 | {
19 | return inputDictionary.ToDictionary(
20 | kvp => kvp.Key,
21 | kvp => ConvertValue(kvp.Value)
22 | );
23 | }
24 |
25 | ///
26 | /// Function that convert a value to a object.
27 | ///
28 | ///
29 | /// A value with real types.
30 | public static object ConvertValue(object value)
31 | {
32 | if (value is JsonElement jsonElement)
33 | switch (jsonElement.ValueKind)
34 | {
35 | case JsonValueKind.String:
36 | return jsonElement.GetString();
37 | case JsonValueKind.Number:
38 | if (jsonElement.TryGetInt32(out var intValue)) return intValue;
39 |
40 | if (jsonElement.TryGetDouble(out var doubleValue)) return doubleValue;
41 | return jsonElement.GetRawText(); // Fallback to string if not int or double
42 | case JsonValueKind.True:
43 | return true;
44 | case JsonValueKind.False:
45 | return false;
46 | case JsonValueKind.Null:
47 | return null;
48 | case JsonValueKind.Object:
49 | return ConvertDictionary(
50 | JsonSerializer
51 | .Deserialize>(jsonElement
52 | .GetRawText())); //Recursive for nested objects
53 | case JsonValueKind.Array:
54 | var array = new List