mirror of https://github.com/open-feature/cli.git
feat: add doc gen, move schema path, add tests, fix react gen (#68)
## This PR - moves JSON schema to a dedicated directory - added schema validation tests - fixed React code gen (and tests) - automate CLI doc generation - Loosen JSON schema - ~~Rename default value~~ ### Related Issues Fixes #66 ### Notes It's a big PR that I could break into smaller changes if necessary. --------- Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com> Signed-off-by: Michael Beemer <michael.beemer@dynatrace.com>
This commit is contained in:
parent
60955af1a9
commit
68a72ee929
|
|
@ -20,15 +20,22 @@ jobs:
|
|||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
- name: Module cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Run tests
|
||||
run: go test ./...
|
||||
|
||||
docs-check:
|
||||
name: Validate docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- run: make generate-docs
|
||||
- name: Check no diff
|
||||
run: |
|
||||
if [ ! -z "$(git status --porcelain)" ]; then echo "::error file=Makefile::Doc generation produced diff. Run 'make generate-docs' and commit results."; exit 1; fi
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
.PHONY: test
|
||||
test:
|
||||
@echo "Running tests..."
|
||||
go test -v ./...
|
||||
@echo "Tests passed successfully!"
|
||||
|
||||
generate-docs:
|
||||
@echo "Generating documentation..."
|
||||
go run ./docs/generate-commands.go
|
||||
@echo "Documentation generated successfully!"
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra/doc"
|
||||
)
|
||||
|
||||
// GenerateDoc generates cobra docs of the cmd
|
||||
func GenerateDoc(path string) error {
|
||||
linkHandler := func(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
filePrepender := func(filename string) string {
|
||||
return "<!-- markdownlint-disable-file -->\n<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->\n"
|
||||
}
|
||||
|
||||
if err := doc.GenMarkdownTreeCustom(rootCmd, path, filePrepender, linkHandler); err != nil {
|
||||
return fmt.Errorf("error generating docs: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
useBooleanFlagDetails,
|
||||
useNumberFlagDetails,
|
||||
useStringFlagDetails,
|
||||
type ReactFlagEvaluationOptions,
|
||||
type ReactFlagEvaluationNoSuspenseOptions,
|
||||
useFlag,
|
||||
useSuspenseFlag,
|
||||
} from "@openfeature/react-sdk";
|
||||
|
||||
/**
|
||||
|
|
@ -14,8 +15,23 @@ import {
|
|||
* - default value: `0.15`
|
||||
* - type: `number`
|
||||
*/
|
||||
export const useDiscountPercentage = (options: Parameters<typeof useNumberFlagDetails>[2]) => {
|
||||
return useNumberFlagDetails("discountPercentage", 0.15, options);
|
||||
export const useDiscountPercentage = (options: ReactFlagEvaluationOptions) => {
|
||||
return useFlag("discountPercentage", 0.15, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Discount percentage applied to purchases.
|
||||
*
|
||||
* **Details:**
|
||||
* - flag key: `discountPercentage`
|
||||
* - default value: `0.15`
|
||||
* - type: `number`
|
||||
*
|
||||
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||
*/
|
||||
export const useSuspenseDiscountPercentage = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||
return useSuspenseFlag("discountPercentage", 0.15, options);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -26,8 +42,23 @@ export const useDiscountPercentage = (options: Parameters<typeof useNumberFlagDe
|
|||
* - default value: `false`
|
||||
* - type: `boolean`
|
||||
*/
|
||||
export const useEnableFeatureA = (options: Parameters<typeof useBooleanFlagDetails>[2]) => {
|
||||
return useBooleanFlagDetails("enableFeatureA", false, options);
|
||||
export const useEnableFeatureA = (options: ReactFlagEvaluationOptions) => {
|
||||
return useFlag("enableFeatureA", false, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Controls whether Feature A is enabled.
|
||||
*
|
||||
* **Details:**
|
||||
* - flag key: `enableFeatureA`
|
||||
* - default value: `false`
|
||||
* - type: `boolean`
|
||||
*
|
||||
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||
*/
|
||||
export const useSuspenseEnableFeatureA = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||
return useSuspenseFlag("enableFeatureA", false, options);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -38,8 +69,23 @@ export const useEnableFeatureA = (options: Parameters<typeof useBooleanFlagDetai
|
|||
* - default value: `Hello there!`
|
||||
* - type: `string`
|
||||
*/
|
||||
export const useGreetingMessage = (options: Parameters<typeof useStringFlagDetails>[2]) => {
|
||||
return useStringFlagDetails("greetingMessage", "Hello there!", options);
|
||||
export const useGreetingMessage = (options: ReactFlagEvaluationOptions) => {
|
||||
return useFlag("greetingMessage", "Hello there!", options);
|
||||
};
|
||||
|
||||
/**
|
||||
* The message to use for greeting users.
|
||||
*
|
||||
* **Details:**
|
||||
* - flag key: `greetingMessage`
|
||||
* - default value: `Hello there!`
|
||||
* - type: `string`
|
||||
*
|
||||
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||
*/
|
||||
export const useSuspenseGreetingMessage = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||
return useSuspenseFlag("greetingMessage", "Hello there!", options);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -50,6 +96,21 @@ export const useGreetingMessage = (options: Parameters<typeof useStringFlagDetai
|
|||
* - default value: `50`
|
||||
* - type: `number`
|
||||
*/
|
||||
export const useUsernameMaxLength = (options: Parameters<typeof useNumberFlagDetails>[2]) => {
|
||||
return useNumberFlagDetails("usernameMaxLength", 50, options);
|
||||
export const useUsernameMaxLength = (options: ReactFlagEvaluationOptions) => {
|
||||
return useFlag("usernameMaxLength", 50, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maximum allowed length for usernames.
|
||||
*
|
||||
* **Details:**
|
||||
* - flag key: `usernameMaxLength`
|
||||
* - default value: `50`
|
||||
* - type: `number`
|
||||
*
|
||||
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||
*/
|
||||
export const useSuspenseUsernameMaxLength = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||
return useSuspenseFlag("usernameMaxLength", 50, options);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
Version string
|
||||
Version = "dev"
|
||||
Commit string
|
||||
Date string
|
||||
)
|
||||
|
|
@ -20,6 +20,7 @@ var rootCmd = &cobra.Command{
|
|||
Use: "openfeature",
|
||||
Short: "CLI for OpenFeature.",
|
||||
Long: `CLI for OpenFeature related functionalities.`,
|
||||
DisableAutoGenTag: true,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<!-- markdownlint-disable-file -->
|
||||
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||
## openfeature
|
||||
|
||||
CLI for OpenFeature.
|
||||
|
||||
### Synopsis
|
||||
|
||||
CLI for OpenFeature related functionalities.
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for openfeature
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [openfeature generate](openfeature_generate.md) - Code generation for flag accessors for OpenFeature.
|
||||
* [openfeature version](openfeature_version.md) - Print the version number of the OpenFeature CLI
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<!-- markdownlint-disable-file -->
|
||||
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||
## openfeature generate
|
||||
|
||||
Code generation for flag accessors for OpenFeature.
|
||||
|
||||
### Synopsis
|
||||
|
||||
Code generation for flag accessors for OpenFeature.
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--flag_manifest_path string Path to the flag manifest.
|
||||
-h, --help help for generate
|
||||
--output_path string Output path for the codegen
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [openfeature](openfeature.md) - CLI for OpenFeature.
|
||||
* [openfeature generate go](openfeature_generate_go.md) - Generate Golang flag accessors for OpenFeature.
|
||||
* [openfeature generate react](openfeature_generate_react.md) - Generate typesafe React Hooks.
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<!-- markdownlint-disable-file -->
|
||||
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||
## openfeature generate go
|
||||
|
||||
Generate Golang flag accessors for OpenFeature.
|
||||
|
||||
### Synopsis
|
||||
|
||||
Generate Golang flag accessors for OpenFeature.
|
||||
|
||||
```
|
||||
openfeature generate go [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for go
|
||||
--package_name string Name of the Go package to be generated.
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--flag_manifest_path string Path to the flag manifest.
|
||||
--output_path string Output path for the codegen
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [openfeature generate](openfeature_generate.md) - Code generation for flag accessors for OpenFeature.
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<!-- markdownlint-disable-file -->
|
||||
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||
## openfeature generate react
|
||||
|
||||
Generate typesafe React Hooks.
|
||||
|
||||
### Synopsis
|
||||
|
||||
Generate typesafe React Hooks compatible with the OpenFeature React SDK.
|
||||
|
||||
```
|
||||
openfeature generate react [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for react
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--flag_manifest_path string Path to the flag manifest.
|
||||
--output_path string Output path for the codegen
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [openfeature generate](openfeature_generate.md) - Code generation for flag accessors for OpenFeature.
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!-- markdownlint-disable-file -->
|
||||
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||
## openfeature version
|
||||
|
||||
Print the version number of the OpenFeature CLI
|
||||
|
||||
```
|
||||
openfeature version [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for version
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [openfeature](openfeature.md) - CLI for OpenFeature.
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/open-feature/cli/cmd"
|
||||
)
|
||||
|
||||
const docPath = "./docs/commands"
|
||||
|
||||
// GenerateDoc generates cobra docs of the cmd
|
||||
func main() {
|
||||
if err := cmd.GenerateDoc(docPath); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
2
go.mod
2
go.mod
|
|
@ -9,12 +9,14 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -1,3 +1,4 @@
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
|
@ -30,6 +31,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
|
||||
flagmanifest "github.com/open-feature/cli/docs/schema/v0"
|
||||
"github.com/open-feature/cli/internal/filesystem"
|
||||
"github.com/open-feature/cli/internal/flagkeys"
|
||||
"github.com/open-feature/cli/internal/generate/types"
|
||||
flagmanifest "github.com/open-feature/cli/schema/v0"
|
||||
|
||||
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||
"github.com/spf13/afero"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package react
|
|||
|
||||
import (
|
||||
_ "embed"
|
||||
"sort"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
|
|
@ -48,32 +47,6 @@ func flagInitParam(flagName string) string {
|
|||
return strconv.Quote(flagName)
|
||||
}
|
||||
|
||||
func flagAccessFunc(t types.FlagType) string {
|
||||
switch t {
|
||||
case types.IntType, types.FloatType:
|
||||
return "useNumberFlagDetails"
|
||||
case types.BoolType:
|
||||
return "useBooleanFlagDetails"
|
||||
case types.StringType:
|
||||
return "useStringFlagDetails"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func supportImports(flags []*types.FlagTmplData) []string {
|
||||
imports := make(map[string]struct{})
|
||||
for _, flag := range flags {
|
||||
imports[flagAccessFunc(flag.Type)] = struct{}{}
|
||||
}
|
||||
var result []string
|
||||
for k := range imports {
|
||||
result = append(result, k)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func defaultValueLiteral(flag *types.FlagTmplData) string {
|
||||
switch flag.Type {
|
||||
case types.StringType:
|
||||
|
|
@ -100,8 +73,6 @@ func (g *genImpl) Generate(input types.Input) error {
|
|||
funcs := template.FuncMap{
|
||||
"FlagVarName": flagVarName,
|
||||
"FlagInitParam": flagInitParam,
|
||||
"FlagAccessFunc": flagAccessFunc,
|
||||
"SupportImports": supportImports,
|
||||
"DefaultValueLiteral": defaultValueLiteral,
|
||||
"TypeString": typeString,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
{{- range $_, $p := SupportImports .Flags}}
|
||||
{{$p}},
|
||||
{{- end}}
|
||||
type ReactFlagEvaluationOptions,
|
||||
type ReactFlagEvaluationNoSuspenseOptions,
|
||||
useFlag,
|
||||
useSuspenseFlag,
|
||||
} from "@openfeature/react-sdk";
|
||||
{{ range .Flags}}
|
||||
/**
|
||||
|
|
@ -11,10 +12,25 @@ import {
|
|||
*
|
||||
* **Details:**
|
||||
* - flag key: `{{ .Name}}`
|
||||
* - default value: `{{ .DefaultValue}}`
|
||||
* - default value: `{{ .DefaultValue }}`
|
||||
* - type: `{{TypeString .Type}}`
|
||||
*/
|
||||
export const use{{FlagVarName .Name}} = (options: Parameters<typeof {{FlagAccessFunc .Type}}>[2]) => {
|
||||
return {{FlagAccessFunc .Type}}({{FlagInitParam .Name}}, {{DefaultValueLiteral .}}, options);
|
||||
export const use{{FlagVarName .Name}} = (options: ReactFlagEvaluationOptions) => {
|
||||
return useFlag({{FlagInitParam .Name}}, {{DefaultValueLiteral .}}, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* {{.Docs}}
|
||||
*
|
||||
* **Details:**
|
||||
* - flag key: `{{ .Name}}`
|
||||
* - default value: `{{ .DefaultValue }}`
|
||||
* - type: `{{TypeString .Type}}`
|
||||
*
|
||||
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||
*/
|
||||
export const useSuspense{{FlagVarName .Name}} = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||
return useSuspenseFlag({{FlagInitParam .Name}}, {{DefaultValueLiteral .}}, options);
|
||||
};
|
||||
{{ end}}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"$id": "https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag_manifest.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Flag Manifest",
|
||||
"description": "Describes a configuration of OpenFeature flags, including info such as their types and default values.",
|
||||
|
|
@ -13,10 +14,13 @@
|
|||
"$ref": "#/$defs/flag"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
"additionalProperties": false,
|
||||
"minProperties": 1
|
||||
}
|
||||
},
|
||||
"required": ["flags"],
|
||||
"required": [
|
||||
"flags"
|
||||
],
|
||||
"$defs": {
|
||||
"flag": {
|
||||
"oneOf": [
|
||||
|
|
@ -36,30 +40,37 @@
|
|||
"$ref": "#/$defs/objectType"
|
||||
}
|
||||
],
|
||||
"required": ["flagType", "defaultValue"]
|
||||
"required": [
|
||||
"flagType",
|
||||
"defaultValue"
|
||||
]
|
||||
},
|
||||
"booleanType": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flagType": {
|
||||
"type": "string",
|
||||
"enum": ["boolean"]
|
||||
"enum": [
|
||||
"boolean"
|
||||
]
|
||||
},
|
||||
"defaultValue": {
|
||||
"description": "The default value returned in code if a flag evaluation is unsuccessful",
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"stringType": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flagType": {
|
||||
"type": "string",
|
||||
"enum": ["string"]
|
||||
"enum": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"defaultValue": {
|
||||
"type": "string"
|
||||
|
|
@ -67,15 +78,16 @@
|
|||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"integerType": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flagType": {
|
||||
"type": "string",
|
||||
"enum": ["integer"]
|
||||
"enum": [
|
||||
"integer"
|
||||
]
|
||||
},
|
||||
"defaultValue": {
|
||||
"type": "integer"
|
||||
|
|
@ -83,15 +95,16 @@
|
|||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"floatType": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flagType": {
|
||||
"type": "string",
|
||||
"enum": ["float"]
|
||||
"enum": [
|
||||
"float"
|
||||
]
|
||||
},
|
||||
"defaultValue": {
|
||||
"type": "number"
|
||||
|
|
@ -99,15 +112,16 @@
|
|||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"objectType": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flagType": {
|
||||
"type": "string",
|
||||
"enum": ["object"]
|
||||
"enum": [
|
||||
"object"
|
||||
]
|
||||
},
|
||||
"defaultValue": {
|
||||
"type": "object"
|
||||
|
|
@ -115,8 +129,7 @@
|
|||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,5 +8,5 @@ import _ "embed"
|
|||
//go:embed flag_manifest.json
|
||||
var Schema string
|
||||
|
||||
// SchemaPath proviees the current path and version of flag manifest.
|
||||
const SchemaPath = "github.com/open-feature/cli/docs/schema/v0/flag_manifest.json"
|
||||
// SchemaPath provides the current path and version of flag manifest.
|
||||
const SchemaPath = "github.com/open-feature/cli/schema/v0/flag_manifest.json"
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package flagmanifest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||
)
|
||||
|
||||
var compiledFlagManifestSchema *jsonschema.Schema
|
||||
|
||||
func init() {
|
||||
sch, err := jsonschema.CompileString(SchemaPath, Schema)
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Errorf("error compiling JSON schema: %v", err))
|
||||
}
|
||||
compiledFlagManifestSchema = sch
|
||||
}
|
||||
|
||||
func TestPositiveFlagManifest(t *testing.T) {
|
||||
if err := walkPath(true, "./tests/positive"); err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegativeFlagManifest(t *testing.T) {
|
||||
if err := walkPath(false, "./tests/negative"); err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func walkPath(shouldPass bool, root string) error {
|
||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ps := strings.Split(path, ".")
|
||||
if ps[len(ps)-1] != "json" {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
if err := json.Unmarshal([]byte(file), &v); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = compiledFlagManifestSchema.Validate(v)
|
||||
|
||||
if (err != nil && shouldPass == true) {
|
||||
return fmt.Errorf("file %s should not have failed validation, but did: %s", path, err)
|
||||
}
|
||||
|
||||
if (err == nil && shouldPass == false) {
|
||||
return fmt.Errorf("file %s should have failed validation, but did not", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"$schema": "../../flag_manifest.json",
|
||||
"flags": {
|
||||
"booleanFlag": {
|
||||
"codeDefault": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "../../flag_manifest.json",
|
||||
"flags": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"$schema": "../../flag_manifest.json",
|
||||
"flags": {
|
||||
"booleanFlag": {
|
||||
"flagType": "boolean",
|
||||
"defaultValue": true
|
||||
},
|
||||
"stringFlag": {
|
||||
"flagType": "string",
|
||||
"defaultValue": "default"
|
||||
},
|
||||
"integerFlag": {
|
||||
"flagType": "integer",
|
||||
"defaultValue": 50
|
||||
},
|
||||
"floatFlag": {
|
||||
"flagType": "float",
|
||||
"defaultValue": 0.15
|
||||
},
|
||||
"objectFlag": {
|
||||
"flagType": "object",
|
||||
"defaultValue": {
|
||||
"primaryColor": "#007bff",
|
||||
"secondaryColor": "#6c757d"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue