{
	"$schema": "http://json-schema.org/draft-07/schema#",
	"$id": "https://raw.githubusercontent.com/juharris/optify/refs/heads/main/schemas/feature_file.json",
	"title": "Optify Feature Configuration",
	"description": "Schema for Optify feature configuration files supporting JSON and YAML formats. See https://github.com/juharris/optify for more information.",
	"type": "object",
	"additionalProperties": false,
	"properties": {
		"metadata": {
			"$ref": "#/definitions/metadata"
		},
		"conditions": {
			"$ref": "#/definitions/conditions"
		},
		"imports": {
			"$ref": "#/definitions/imports"
		},
		"options": {
			"$ref": "#/definitions/options"
		}
	},
	"minProperties": 1,
	"definitions": {
		"condition": {
			"type": "object",
			"oneOf": [
				{
					"properties": {
						"jsonPointer": {
							"$ref": "#/definitions/jsonPointer"
						},
						"equals": {
							"description": "Value to compare for equality. It can be any JSON value."
						}
					},
					"required": [
						"jsonPointer",
						"equals"
					],
					"additionalProperties": false
				},
				{
					"properties": {
						"jsonPointer": {
							"$ref": "#/definitions/jsonPointer"
						},
						"matches": {
							"type": "string",
							"description": "Regular expression pattern to match against.",
							"default": "^(?:value)$"
						}
					},
					"required": [
						"jsonPointer",
						"matches"
					],
					"additionalProperties": false
				},
				{
					"properties": {
						"and": {
							"description": "All conditions must be true for the group to be true.",
							"type": "array",
							"items": {
								"$ref": "#/definitions/condition"
							},
							"minItems": 1,
							"uniqueItems": true,
							"default": [
								{}
							]
						}
					},
					"required": [
						"and"
					],
					"additionalProperties": false
				},
				{
					"properties": {
						"or": {
							"description": "At least one condition must be true for the group to be true.",
							"type": "array",
							"items": {
								"$ref": "#/definitions/condition"
							},
							"minItems": 1,
							"uniqueItems": true,
							"default": [
								{}
							]
						}
					},
					"required": [
						"or"
					],
					"additionalProperties": false
				},
				{
					"properties": {
						"not": {
							"description": "The condition must be false for the group to be true.",
							"$ref": "#/definitions/condition"
						}
					},
					"required": [
						"not"
					],
					"additionalProperties": false
				}
			]
		},
		"conditions": {
			"$ref": "#/definitions/condition",
			"description": "Conditions to enable this feature file when it is requested and when constraints are given. These conditions are meant for temporary experimental features that should only be enabled in some requests.\n\nIf no constraints are given, then these conditions are ignored. Most projects should either always use constraints in every request or never use constraints in order to avoid confusion.\n\nConditions cannot be used in imported features. This helps keep retrieving and building configuration options for a list of features fast and more predictable because imports do not need to be re-evaluated. Instead, keep each feature file as granular and self-contained as possible, then use conditions and import the required granular features in a feature file that defines a common scenario.\n\nSee https://github.com/juharris/optify?tab=readme-ov-file#conditions for more information and examples."
		},
		"configurableString": {
			"description": "A string or a template that can be filled in when the configuration is built. There are no required properties because files that only override arguments should not need to re-specify the type and base value.\n\nSee https://github.com/juharris/optify/blob/main/docs/ConfigurableStrings.md for details.",
			"anyOf": [
				{
					"type": "null"
				},
				{
					"type": "string"
				},
				{
					"type": "object",
					"properties": {
						"$type": {
							"type": "string",
							"description": "The type for this object to help Optify determine if pre-processing is needed.",
							"enum": [
								"Optify.ConfigurableString"
							]
						},
						"base": {
							"description": "The base value to use as the starting point or root for the configurable string. It can be a string or an object to use a file or a liquid template.",
							"$ref": "#/definitions/configurableStringReplacementValue"
						},
						"arguments": {
							"type": "object",
							"description": "The arguments to use to fill the base template. Each argument can be a string or an object.\n\nUse empty strings instead of `null`. Values cannot be `null` because it's not clear what should be done with them and how they should be inserted into the template.",
							"additionalProperties": {
								"$ref": "#/definitions/configurableStringReplacementValue"
							}
						}
					},
					"additionalProperties": false
				}
			]
		},
		"configurableStringReplacementValue": {
			"anyOf": [
				{
					"type": "string"
				},
				{
					"type": "object",
					"properties": {
						"file": {
							"type": "string",
							"description": "The relative path to a file with contents to use as a replacement value.\n\nCertain types of files will be handled specially: .liquid files will be treated as liquid templates and arguments can be used to recursively fill the template."
						}
					},
					"required": [
						"file"
					],
					"additionalProperties": false
				},
				{
					"type": "object",
					"properties": {
						"liquid": {
							"type": "string",
							"description": "A liquid template to use as the value. The template can use arguments to recursively fill the template."
						}
					},
					"required": [
						"liquid"
					],
					"additionalProperties": false
				}
			]
		},
		"imports": {
			"type": "array",
			"description": "List of canonical feature names to inherit from.\n\nCanonical feature names are derived from the relative path to the file from the root of the configuration files, but without the file extension in order to keep canonical feature names clear.\n\nImports are applied in order. I.e., later imports override earlier imports.\n\nAliases cannot be use to refer to an imported feature.\n\nSee https://github.com/juharris/optify?tab=readme-ov-file#inheritance for more information and examples.",
			"items": {
				"type": "string",
				"minLength": 1,
				"pattern": "^(?!.*\\.(json|yaml|yml)$).*$",
				"errorMessage": "Import paths should not include file extensions.\n\nFile extensions are not a part of feature names. The builder does not allow two files with the same path but different extensions in order to keep canonical feature names clear."
			},
			"default": [
				"<path/to/file without extension>"
			],
			"minItems": 1
		},
		"jsonPointer": {
			"type": "string",
			"description": "A JSON Pointer to a value in the constraints of a request.\n\nFor example, `/domain` or `/myFlags/1`. See https://datatracker.ietf.org/doc/html/rfc6901 for details.",
			"default": "/path/to/value"
		},
		"metadata": {
			"type": "object",
			"description": "Information about the feature configuration.",
			"properties": {
				"aliases": {
					"type": "array",
					"description": "Alternative names for the group of options. Helpful for using custom short names or obfuscating the feature name in production.\n\nAlias names must be unique across all feature files.\n\nAliases cannot be use to refer to an imported feature. The canonical feature name (based on the file path without extension) must be used instead.",
					"items": {
						"type": "string",
						"minLength": 1
					},
					"default": [
						"<alias>"
					],
					"uniqueItems": true
				},
				"details": {
					"description": "Other metadata that may be custom and application specific. Good place for information that should be available to the application programmatically."
				},
				"owners": {
					"type": "string",
					"description": "The creators or maintainers of this group of options. For example, emails separated by semicolons.",
					"minLength": 1,
					"default": "owner@company.com"
				}
			}
		},
		"options": {
			"minProperties": 1,
			"description": "The actual configuration options. The value for each key can be any of the following: object, array, string, number, boolean, or null.",
			"type": "object"
		}
	}
}