From f10d87205dd6a21222de362694d208fd293d9200 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Tue, 2 Jul 2024 10:26:24 -0400 Subject: [PATCH] chore: fix pmd violations (#856) Signed-off-by: Todd Baert --- .../envvar/EnvironmentKeyTransformer.java | 2 +- .../contrib/providers/flagd/FlagdOptions.java | 60 +- .../flagd/resolver/grpc/GrpcResolver.java | 32 +- .../grpc/strategy/TracedResolving.java | 1 + .../process/targeting/Fractional.java | 7 +- providers/flagd/src/main/resources/flags.json | 184 ++++++ .../flagd/src/main/resources/targeting.json | 589 ++++++++++++++++++ providers/statsig/pom.xml | 2 +- 8 files changed, 814 insertions(+), 63 deletions(-) create mode 100644 providers/flagd/src/main/resources/flags.json create mode 100644 providers/flagd/src/main/resources/targeting.json diff --git a/providers/env-var/src/main/java/dev/openfeature/contrib/providers/envvar/EnvironmentKeyTransformer.java b/providers/env-var/src/main/java/dev/openfeature/contrib/providers/envvar/EnvironmentKeyTransformer.java index 1b68fd6f5..10f0b73b7 100644 --- a/providers/env-var/src/main/java/dev/openfeature/contrib/providers/envvar/EnvironmentKeyTransformer.java +++ b/providers/env-var/src/main/java/dev/openfeature/contrib/providers/envvar/EnvironmentKeyTransformer.java @@ -101,7 +101,7 @@ public static EnvironmentKeyTransformer replaceDotWithUnderscoreTransformer() { public static EnvironmentKeyTransformer hyphenCaseToScreamingSnake() { return new EnvironmentKeyTransformer(REPLACE_HYPHEN_WITH_UNDERSCORE) - .andThen(EnvironmentKeyTransformer.toUpperCaseTransformer()); + .andThen(toUpperCaseTransformer()); } public static EnvironmentKeyTransformer doNothing() { diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java index 2f4e20b9d..996de9d43 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java @@ -5,25 +5,6 @@ import lombok.Builder; import lombok.Getter; -import static dev.openfeature.contrib.providers.flagd.Config.BASE_EVENT_STREAM_RETRY_BACKOFF_MS; -import static dev.openfeature.contrib.providers.flagd.Config.BASE_EVENT_STREAM_RETRY_BACKOFF_MS_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.CACHE_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.DEADLINE_MS_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_CACHE; -import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_DEADLINE; -import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_HOST; -import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_MAX_CACHE_SIZE; -import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_MAX_EVENT_STREAM_RETRIES; -import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_TLS; -import static dev.openfeature.contrib.providers.flagd.Config.HOST_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.MAX_CACHE_SIZE_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.MAX_EVENT_STREAM_RETRIES_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.OFFLINE_SOURCE_PATH; -import static dev.openfeature.contrib.providers.flagd.Config.PORT_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.SERVER_CERT_PATH_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.SOCKET_PATH_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.SOURCE_SELECTOR_ENV_VAR_NAME; -import static dev.openfeature.contrib.providers.flagd.Config.TLS_ENV_VAR_NAME; import static dev.openfeature.contrib.providers.flagd.Config.fallBackToEnvOrDefault; import static dev.openfeature.contrib.providers.flagd.Config.fromValueProvider; @@ -44,7 +25,7 @@ public class FlagdOptions { * flagd connection host. */ @Builder.Default - private String host = fallBackToEnvOrDefault(HOST_ENV_VAR_NAME, DEFAULT_HOST); + private String host = fallBackToEnvOrDefault(Config.HOST_ENV_VAR_NAME, Config.DEFAULT_HOST); /** * flagd connection port. @@ -55,75 +36,76 @@ public class FlagdOptions { * Use TLS connectivity. */ @Builder.Default - private boolean tls = Boolean.parseBoolean(fallBackToEnvOrDefault(TLS_ENV_VAR_NAME, DEFAULT_TLS)); + private boolean tls = Boolean.parseBoolean(fallBackToEnvOrDefault(Config.TLS_ENV_VAR_NAME, Config.DEFAULT_TLS)); /** * TLS certificate overriding if TLS connectivity is used. */ @Builder.Default - private String certPath = fallBackToEnvOrDefault(SERVER_CERT_PATH_ENV_VAR_NAME, null); + private String certPath = fallBackToEnvOrDefault(Config.SERVER_CERT_PATH_ENV_VAR_NAME, null); /** * Unix socket path to flagd. */ @Builder.Default - private String socketPath = fallBackToEnvOrDefault(SOCKET_PATH_ENV_VAR_NAME, null); + private String socketPath = fallBackToEnvOrDefault(Config.SOCKET_PATH_ENV_VAR_NAME, null); /** * Cache type to use. Supports - lru, disabled. */ @Builder.Default - private String cacheType = fallBackToEnvOrDefault(CACHE_ENV_VAR_NAME, DEFAULT_CACHE); + private String cacheType = fallBackToEnvOrDefault(Config.CACHE_ENV_VAR_NAME, Config.DEFAULT_CACHE); /** * Max cache size. */ @Builder.Default - private int maxCacheSize = fallBackToEnvOrDefault(MAX_CACHE_SIZE_ENV_VAR_NAME, DEFAULT_MAX_CACHE_SIZE); + private int maxCacheSize = fallBackToEnvOrDefault(Config.MAX_CACHE_SIZE_ENV_VAR_NAME, + Config.DEFAULT_MAX_CACHE_SIZE); /** * Max event stream connection retries. */ @Builder.Default - private int maxEventStreamRetries = - fallBackToEnvOrDefault(MAX_EVENT_STREAM_RETRIES_ENV_VAR_NAME, DEFAULT_MAX_EVENT_STREAM_RETRIES); + private int maxEventStreamRetries = fallBackToEnvOrDefault(Config.MAX_EVENT_STREAM_RETRIES_ENV_VAR_NAME, + Config.DEFAULT_MAX_EVENT_STREAM_RETRIES); /** * Backoff interval in milliseconds. */ @Builder.Default - private int retryBackoffMs = - fallBackToEnvOrDefault(BASE_EVENT_STREAM_RETRY_BACKOFF_MS_ENV_VAR_NAME, BASE_EVENT_STREAM_RETRY_BACKOFF_MS); - + private int retryBackoffMs = fallBackToEnvOrDefault(Config.BASE_EVENT_STREAM_RETRY_BACKOFF_MS_ENV_VAR_NAME, + Config.BASE_EVENT_STREAM_RETRY_BACKOFF_MS); /** * Connection deadline in milliseconds. - * For RPC resolving, this is the deadline to connect to flagd for flag evaluation. + * For RPC resolving, this is the deadline to connect to flagd for flag + * evaluation. * For in-process resolving, this is the deadline for sync stream termination. */ @Builder.Default - private int deadline = fallBackToEnvOrDefault(DEADLINE_MS_ENV_VAR_NAME, DEFAULT_DEADLINE); + private int deadline = fallBackToEnvOrDefault(Config.DEADLINE_MS_ENV_VAR_NAME, Config.DEFAULT_DEADLINE); /** * Selector to be used with flag sync gRPC contract. **/ @Builder.Default - private String selector = fallBackToEnvOrDefault(SOURCE_SELECTOR_ENV_VAR_NAME, null); + private String selector = fallBackToEnvOrDefault(Config.SOURCE_SELECTOR_ENV_VAR_NAME, null); /** * File source of flags to be used by offline mode. * Setting this enables the offline mode of the in-process provider. */ @Builder.Default - private String offlineFlagSourcePath = fallBackToEnvOrDefault(OFFLINE_SOURCE_PATH, null); + private String offlineFlagSourcePath = fallBackToEnvOrDefault(Config.OFFLINE_SOURCE_PATH, null); /** - * Inject OpenTelemetry for the library runtime. Providing sdk will initiate distributed tracing for flagd grpc + * Inject OpenTelemetry for the library runtime. Providing sdk will initiate + * distributed tracing for flagd grpc * connectivity. */ private OpenTelemetry openTelemetry; - /** * Builder overwrite in order to customize the "build" method. * @@ -144,7 +126,8 @@ public FlagdOptions build() { */ public static class FlagdOptionsBuilder { /** - * Enable OpenTelemetry instance extraction from GlobalOpenTelemetry. Note that, this is only useful if global + * Enable OpenTelemetry instance extraction from GlobalOpenTelemetry. Note that, + * this is only useful if global * configurations are registered. */ public FlagdOptionsBuilder withGlobalTelemetry(final boolean b) { @@ -160,7 +143,8 @@ void prebuild() { } if (port == 0) { - port = Integer.parseInt(fallBackToEnvOrDefault(PORT_ENV_VAR_NAME, determineDefaultPortForResolver())); + port = Integer + .parseInt(fallBackToEnvOrDefault(Config.PORT_ENV_VAR_NAME, determineDefaultPortForResolver())); } } diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcResolver.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcResolver.java index 020fc9839..9879d6c55 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcResolver.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcResolver.java @@ -1,13 +1,6 @@ package dev.openfeature.contrib.providers.flagd.resolver.grpc; -import static dev.openfeature.contrib.providers.flagd.Config.CACHED_REASON; -import static dev.openfeature.contrib.providers.flagd.Config.CONTEXT_FIELD; -import static dev.openfeature.contrib.providers.flagd.Config.FLAG_KEY_FIELD; -import static dev.openfeature.contrib.providers.flagd.Config.METADATA_FIELD; -import static dev.openfeature.contrib.providers.flagd.Config.REASON_FIELD; -import static dev.openfeature.contrib.providers.flagd.Config.STATIC_REASON; -import static dev.openfeature.contrib.providers.flagd.Config.VALUE_FIELD; -import static dev.openfeature.contrib.providers.flagd.Config.VARIANT_FIELD; +import dev.openfeature.contrib.providers.flagd.Config; import java.util.HashMap; import java.util.List; @@ -157,15 +150,15 @@ private ProviderEvaluation fromCache = this.cache.get(key); if (fromCache != null) { - fromCache.setReason(CACHED_REASON); + fromCache.setReason(Config.CACHED_REASON); return (ProviderEvaluation) fromCache; } } // build the gRPC request Message req = request.newBuilderForType() - .setField(getFieldDescriptor(request, FLAG_KEY_FIELD), key) - .setField(getFieldDescriptor(request, CONTEXT_FIELD), convertContext(ctx)) + .setField(getFieldDescriptor(request, Config.FLAG_KEY_FIELD), key) + .setField(getFieldDescriptor(request, Config.CONTEXT_FIELD), convertContext(ctx)) .build(); final Message response; @@ -178,16 +171,16 @@ private ProviderEvaluation result = ProviderEvaluation.builder() .value(value) - .variant(getField(response, VARIANT_FIELD)) - .reason(getField(response, REASON_FIELD)) + .variant(getField(response, Config.VARIANT_FIELD)) + .reason(getField(response, Config.REASON_FIELD)) .flagMetadata(immutableMetadata) .build(); @@ -202,7 +195,7 @@ private ProviderEvaluation Boolean isEvaluationCacheable(ProviderEvaluation evaluation) { String reason = evaluation.getReason(); - return reason != null && reason.equals(STATIC_REASON) && this.cacheAvailable(); + return reason != null && reason.equals(Config.STATIC_REASON) && this.cacheAvailable(); } private Boolean cacheAvailable() { @@ -221,7 +214,8 @@ private static Value convertObjectResponse(Struct protobuf) { */ private static Struct convertContext(EvaluationContext ctx) { Map ctxMap = ctx.asMap(); - // asMap() does not provide explicitly set targeting key (ex:- new ImmutableContext("TargetingKey") ). + // asMap() does not provide explicitly set targeting key (ex:- new + // ImmutableContext("TargetingKey") ). // Hence, we add this explicitly here for targeting rule processing. ctxMap.put("targetingKey", new Value(ctx.getTargetingKey())); @@ -348,7 +342,7 @@ private static Descriptors.FieldDescriptor getFieldDescriptor(Message message, S } private static ImmutableMetadata metadataFromResponse(Message response) { - final Object metadata = response.getField(getFieldDescriptor(response, METADATA_FIELD)); + final Object metadata = response.getField(getFieldDescriptor(response, Config.METADATA_FIELD)); if (!(metadata instanceof Struct)) { return ImmutableMetadata.builder().build(); @@ -373,7 +367,7 @@ private static ImmutableMetadata metadataFromResponse(Message response) { private OpenFeatureError mapError(Exception e) { if (e instanceof StatusRuntimeException) { - Code code = ((StatusRuntimeException)e).getStatus().getCode(); + Code code = ((StatusRuntimeException) e).getStatus().getCode(); switch (code) { case DATA_LOSS: return new ParseError(e.getMessage()); diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/strategy/TracedResolving.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/strategy/TracedResolving.java index 1ea1eea81..b2addfd8b 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/strategy/TracedResolving.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/strategy/TracedResolving.java @@ -13,6 +13,7 @@ /** * {@link TracedResolving} a request to response resolver with tracing for telemetry. */ +@SuppressWarnings("PMD.UnusedLocalVariable") public class TracedResolving implements ResolveStrategy { private final Tracer tracer; diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting/Fractional.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting/Fractional.java index 33693b62f..1530dbe96 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting/Fractional.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting/Fractional.java @@ -70,11 +70,10 @@ public Object evaluate(List arguments, Object data) throws JsonLogicEvaluationEx private static String distributeValue( final String hashKey, final List propertyList, - int totalWeight - ) throws JsonLogicEvaluationException { + int totalWeight) throws JsonLogicEvaluationException { byte[] bytes = hashKey.getBytes(StandardCharsets.UTF_8); int mmrHash = MurmurHash3.hash32x86(bytes, 0, bytes.length, 0); - float bucket = (Math.abs(mmrHash) * 1.0f / Integer.MAX_VALUE) * 100; + float bucket = Math.abs(mmrHash) * 1.0f / Integer.MAX_VALUE * 100; float bucketSum = 0; for (FractionProperty p : propertyList) { @@ -90,7 +89,7 @@ private static String distributeValue( } @Getter - @SuppressWarnings({"checkstyle:NoFinalizer"}) + @SuppressWarnings({ "checkstyle:NoFinalizer" }) private static class FractionProperty { private final String variant; private final int weight; diff --git a/providers/flagd/src/main/resources/flags.json b/providers/flagd/src/main/resources/flags.json new file mode 100644 index 000000000..a6a09a7ee --- /dev/null +++ b/providers/flagd/src/main/resources/flags.json @@ -0,0 +1,184 @@ +{ + "$id": "https://flagd.dev/schema/v0/flags.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "flagd Flag Configuration", + "description": "Defines flags for use in flagd, including typed variants and rules", + "type": "object", + "properties": { + "flags": { + "title": "Flags", + "description": "Top-level flags object. All flags are defined here.", + "type": "object", + "$comment": "flag objects are one of the 4 flag types defined in definitions", + "additionalProperties": false, + "patternProperties": { + "^.{1,}$": { + "oneOf": [ + { + "title": "Boolean flag", + "description": "A flag having boolean values.", + "$ref": "#/definitions/booleanFlag" + }, + { + "title": "String flag", + "description": "A flag having string values.", + "$ref": "#/definitions/stringFlag" + }, + { + "title": "Numeric flag", + "description": "A flag having numeric values.", + "$ref": "#/definitions/numberFlag" + }, + { + "title": "Object flag", + "description": "A flag having arbitrary object values.", + "$ref": "#/definitions/objectFlag" + } + ] + } + } + }, + "$evaluators": { + "title": "Evaluators", + "description": "Reusable targeting rules that can be referenced with \"$ref\": \"myRule\" in multiple flags.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.{1,}$": { + "$comment": "this relative ref means that targeting.json MUST be in the same dir, or available on the same HTTP path", + "$ref": "./targeting.json#/definitions/targeting" + } + } + } + }, + "definitions": { + "flag": { + "$comment": "base flag object; no title/description here, allows for better UX, keep it in the overrides", + "type": "object", + "properties": { + "state": { + "title": "Flag State", + "description": "Indicates whether the flag is functional. Disabled flags are treated as if they don't exist.", + "type": "string", + "enum": [ + "ENABLED", + "DISABLED" + ] + }, + "defaultVariant": { + "title": "Default Variant", + "description": "The variant to serve if no dynamic targeting applies (including if the targeting returns null).", + "type": "string" + }, + "targeting": { + "$ref": "./targeting.json#/definitions/targeting" + } + }, + "required": [ + "state", + "defaultVariant" + ] + }, + "booleanVariants": { + "type": "object", + "properties": { + "variants": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.{1,}$": { + "type": "boolean" + } + }, + "default": { + "true": true, + "false": false + } + } + } + }, + "stringVariants": { + "type": "object", + "properties": { + "variants": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.{1,}$": { + "type": "string" + } + } + } + } + }, + "numberVariants": { + "type": "object", + "properties": { + "variants": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.{1,}$": { + "type": "number" + } + } + } + } + }, + "objectVariants": { + "type": "object", + "properties": { + "variants": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.{1,}$": { + "type": "object" + } + } + } + } + }, + "booleanFlag": { + "$comment": "merge the variants with the base flag to build our typed flags", + "allOf": [ + { + "$ref": "#/definitions/flag" + }, + { + "$ref": "#/definitions/booleanVariants" + } + ] + }, + "stringFlag": { + "allOf": [ + { + "$ref": "#/definitions/flag" + }, + { + "$ref": "#/definitions/stringVariants" + } + ] + }, + "numberFlag": { + "allOf": [ + { + "$ref": "#/definitions/flag" + }, + { + "$ref": "#/definitions/numberVariants" + } + ] + }, + "objectFlag": { + "allOf": [ + { + "$ref": "#/definitions/flag" + }, + { + "$ref": "#/definitions/objectVariants" + } + ] + } + } +} diff --git a/providers/flagd/src/main/resources/targeting.json b/providers/flagd/src/main/resources/targeting.json new file mode 100644 index 000000000..3818343a6 --- /dev/null +++ b/providers/flagd/src/main/resources/targeting.json @@ -0,0 +1,589 @@ +{ + "$id": "https://flagd.dev/schema/v0/targeting.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "flagd Targeting", + "description": "Defines targeting logic for flagd; a extension of JSONLogic, including purpose-built feature-flagging operations.", + "type": "object", + "definitions": { + "targeting": { + "title": "Targeting", + "description": "An expression returning a value which is coerced to a string to be used as a targeting key, or null (to fall back to defaultVariant). If targeting returns a value which is not a variant key, it's considered an error.", + "anyOf": [ + { + "$comment": "we need this to support empty targeting", + "type": "object", + "additionalProperties": false, + "properties": {} + }, + { + "$ref": "#/definitions/anyRule" + } + ] + }, + "primitive": { + "oneOf": [ + { + "description": "When returned from rules, a null value \"exits\", the targeting, and the \"defaultValue\" is returned, with the reason indicating the targeting did not match.", + "type": "null" + }, + { + "description": "When returned from rules, booleans are converted to strings (\"true\"/\"false\"), and used to as keys to retrieve the associated value from the \"variants\" object. Be sure that the returned string is present as a key in the variants!", + "type": "boolean" + }, + { + "description": "When returned from rules, the behavior of numbers is not defined.", + "type": "number" + }, + { + "description": "When returned from rules, strings are used to as keys to retrieve the associated value from the \"variants\" object. Be sure that the returned string is present as a key in the variants!.", + "type": "string" + }, + { + "description": "When returned from rules, strings are used to as keys to retrieve the associated value from the \"variants\" object. Be sure that the returned string is present as a key in the variants!.", + "type": "array" + } + ] + }, + "varRule": { + "title": "Var Operation", + "description": "Retrieve data from the provided data object.", + "type": "object", + "additionalProperties": false, + "properties": { + "var": { + "anyOf": [ + { + "type": "string", + "description": "flagd automatically injects \"$flagd.timestamp\" (unix epoch) and \"$flagd.flagKey\" (the key of the flag in evaluation) into the context.", + "pattern": "^\\$flagd\\.((timestamp)|(flagKey))$" + }, + { + "not": { + "$comment": "this is a negated (not) match of \"$flagd.{some-key}\", which is faster and more compatible that a negative lookahead regex", + "type": "string", + "description": "flagd automatically injects \"$flagd.timestamp\" (unix epoch) and \"$flagd.flagKey\" (the key of the flag in evaluation) into the context.", + "pattern": "^\\$flagd\\..*$" + } + }, + { + "type": "array", + "$comment": "this is to support the form of var with a default... there seems to be a bug here, where ajv gives a warning (not an error) because maxItems doesn't equal the number of entries in items, though this is valid in this case", + "minItems": 1, + "items": [ + { + "type": "string" + } + ], + "additionalItems": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "number" + } + ] + } + } + ] + } + } + }, + "missingRule": { + "title": "Missing Operation", + "description": "Takes an array of data keys to search for (same format as var). Returns an array of any keys that are missing from the data object, or an empty array.", + "type": "object", + "additionalProperties": false, + "properties": { + "missing": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "missingSomeRule": { + "title": "Missing-Some Operation", + "description": "Takes a minimum number of data keys that are required, and an array of keys to search for (same format as var or missing). Returns an empty array if the minimum is met, or an array of the missing keys otherwise.", + "type": "object", + "additionalProperties": false, + "properties": { + "missing_some": { + "minItems": 2, + "maxItems": 2, + "type": "array", + "items": [ + { + "type": "number" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "binaryOrTernaryOp": { + "type": "array", + "minItems": 2, + "maxItems": 3, + "items": { + "$ref": "#/definitions/args" + } + }, + "binaryOrTernaryRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "substr": { + "title": "Substring Operation", + "description": "Get a portion of a string. Give a positive start position to return everything beginning at that index. Give a negative start position to work backwards from the end of the string, then return everything. Give a positive length to express how many characters to return.", + "$ref": "#/definitions/binaryOrTernaryOp" + }, + "<": { + "title": "Less-Than/Between Operation. Can be used to test that one value is between two others.", + "$ref": "#/definitions/binaryOrTernaryOp" + }, + "<=": { + "title": "Less-Than-Or-Equal-To/Between Operation. Can be used to test that one value is between two others.", + "$ref": "#/definitions/binaryOrTernaryOp" + } + } + }, + "binaryOp": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "$ref": "#/definitions/args" + } + }, + "binaryRule": { + "title": "Binary Operation", + "description": "Any primitive JSONLogic operation with 2 operands.", + "type": "object", + "additionalProperties": false, + "properties": { + "if": { + "title": "If Operator", + "description": "The if statement takes 1 or more arguments: a condition (\"if\"), what to do if its true (\"then\", optional, defaults to returning true), and what to do if its false (\"else\", optional, defaults to returning false). Note that the else condition can be used as an else-if statement by adding additional arguments.", + "$ref": "#/definitions/variadicOp" + }, + "==": { + "title": "Lose Equality Operation", + "description": "Tests equality, with type coercion. Requires two arguments.", + "$ref": "#/definitions/binaryOp" + }, + "===": { + "title": "Strict Equality Operation", + "description": "Tests strict equality. Requires two arguments.", + "$ref": "#/definitions/binaryOp" + }, + "!=": { + "title": "Lose Inequality Operation", + "description": "Tests not-equal, with type coercion.", + "$ref": "#/definitions/binaryOp" + }, + "!==": { + "title": "Strict Inequality Operation", + "description": "Tests strict not-equal.", + "$ref": "#/definitions/binaryOp" + }, + ">": { + "title": "Greater-Than Operation", + "$ref": "#/definitions/binaryOp" + }, + ">=": { + "title": "Greater-Than-Or-Equal-To Operation", + "$ref": "#/definitions/binaryOp" + }, + "%": { + "title": "Modulo Operation", + "description": "Finds the remainder after the first argument is divided by the second argument.", + "$ref": "#/definitions/binaryOp" + }, + "/": { + "title": "Division Operation", + "$ref": "#/definitions/binaryOp" + }, + "map": { + "title": "Map Operation", + "description": "Perform an action on every member of an array. Note, that inside the logic being used to map, var operations are relative to the array element being worked on.", + "$ref": "#/definitions/binaryOp" + }, + "filter": { + "title": "Filter Operation", + "description": "Keep only elements of the array that pass a test. Note, that inside the logic being used to filter, var operations are relative to the array element being worked on.", + "$ref": "#/definitions/binaryOp" + }, + "all": { + "title": "All Operation", + "description": "Perform a test on each member of that array, returning true if all pass. Inside the test code, var operations are relative to the array element being tested.", + "$ref": "#/definitions/binaryOp" + }, + "none": { + "title": "None Operation", + "description": "Perform a test on each member of that array, returning true if none pass. Inside the test code, var operations are relative to the array element being tested.", + "$ref": "#/definitions/binaryOp" + }, + "some": { + "title": "Some Operation", + "description": "Perform a test on each member of that array, returning true if some pass. Inside the test code, var operations are relative to the array element being tested.", + "$ref": "#/definitions/binaryOp" + }, + "in": { + "title": "In Operation", + "description": "If the second argument is an array, tests that the first argument is a member of the array.", + "$ref": "#/definitions/binaryOp" + } + } + }, + "reduceRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "reduce": { + "title": "Reduce Operation", + "description": "Combine all the elements in an array into a single value, like adding up a list of numbers. Note, that inside the logic being used to reduce, var operations only have access to an object with a \"current\" and a \"accumulator\".", + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "$ref": "#/definitions/args" + } + } + } + }, + "associativeOp": { + "type": "array", + "minItems": 2, + "items": { + "$ref": "#/definitions/args" + } + }, + "associativeRule": { + "title": "Mathematically Associative Operation", + "description": "Operation applicable to 2 or more parameters.", + "type": "object", + "additionalProperties": false, + "properties": { + "*": { + "title": "Multiplication Operation", + "description": "Multiplication; associative, will accept and unlimited amount of arguments.", + "$ref": "#/definitions/associativeOp" + } + } + }, + "unaryOp": { + "anyOf": [ + { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/definitions/args" + } + }, + { + "$ref": "#/definitions/args" + } + ] + }, + "unaryRule": { + "title": "Unary Operation", + "description": "Any primitive JSONLogic operation with 1 operands.", + "type": "object", + "additionalProperties": false, + "properties": { + "!": { + "title": "Negation Operation", + "description": "Logical negation (“not”). Takes just one argument.", + "$ref": "#/definitions/unaryOp" + }, + "!!": { + "title": "Double Negation Operation", + "description": "Double negation, or 'cast to a boolean'. Takes a single argument.", + "$ref": "#/definitions/unaryOp" + } + } + }, + "variadicOp": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/args" + } + }, + "variadicRule": { + "$comment": "note < and <= can be used with up to 3 ops (between)", + "type": "object", + "additionalProperties": false, + "properties": { + "or": { + "title": "Or Operation", + "description": "Simple boolean test, with 1 or more arguments. At a more sophisticated level, \"or\" returns the first truthy argument, or the last argument.", + "$ref": "#/definitions/variadicOp" + }, + "and": { + "title": "", + "description": "Simple boolean test, with 1 or more arguments. At a more sophisticated level, \"and\" returns the first falsy argument, or the last argument.", + "$ref": "#/definitions/variadicOp" + }, + "+": { + "title": "Addition Operation", + "description": "Addition; associative, will accept and unlimited amount of arguments.", + "$ref": "#/definitions/variadicOp" + }, + "-": { + "title": "Subtraction Operation", + "$ref": "#/definitions/variadicOp" + }, + "max": { + "title": "Maximum Operation", + "description": "Return the maximum from a list of values.", + "$ref": "#/definitions/variadicOp" + }, + "min": { + "title": "Minimum Operation", + "description": "Return the minimum from a list of values.", + "$ref": "#/definitions/variadicOp" + }, + "merge": { + "title": "Merge Operation", + "description": "Takes one or more arrays, and merges them into one array. If arguments aren't arrays, they get cast to arrays.", + "$ref": "#/definitions/variadicOp" + }, + "cat": { + "title": "Concatenate Operation", + "description": "Concatenate all the supplied arguments. Note that this is not a join or implode operation, there is no “glue” string.", + "$ref": "#/definitions/variadicOp" + } + } + }, + "stringCompareArg": { + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/anyRule" + } + ] + }, + "stringCompareArgs": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "$ref": "#/definitions/stringCompareArg" + } + }, + "stringCompareRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "starts_with": { + "title": "Starts-With Operation", + "description": "The string attribute starts with the specified string value.", + "$ref": "#/definitions/stringCompareArgs" + }, + "ends_with": { + "title": "Ends-With Operation", + "description": "The string attribute ends with the specified string value.", + "$ref": "#/definitions/stringCompareArgs" + } + } + }, + "semVerString": { + "title": "Semantic Version String", + "description": "A string representing a valid semantic version expression as per https://semver.org/.", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "ruleSemVer": { + "type": "object", + "additionalProperties": false, + "properties": { + "sem_ver": { + "title": "Semantic Version Operation", + "description": "Attribute matches a semantic version condition. Accepts \"npm-style\" range specifiers: \"=\", \"!=\", \">\", \"<\", \">=\", \"<=\", \"~\" (match minor version), \"^\" (match major version).", + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": [ + { + "oneOf": [ + { + "$ref": "#/definitions/semVerString" + }, + { + "$ref": "#/definitions/varRule" + } + ] + }, + { + "description": "Range specifiers: \"=\", \"!=\", \">\", \"<\", \">=\", \"<=\", \"~\" (match minor version), \"^\" (match major version).", + "enum": [ + "=", + "!=", + ">", + "<", + ">=", + "<=", + "~", + "^" + ] + }, + { + "oneOf": [ + { + "$ref": "#/definitions/semVerString" + }, + { + "$ref": "#/definitions/varRule" + } + ] + } + ] + } + } + }, + "fractionalWeightArg": { + "$comment": "if we remove the \"sum to 100\" restriction, update the descriptions below!", + "description": "Distribution for all possible variants, with their associated weighting out of 100.", + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "description": "If this bucket is randomly selected, this string is used to as a key to retrieve the associated value from the \"variants\" object.", + "type": "string" + }, + { + "description": "Weighted distribution for this variant key (must sum to 100).", + "type": "number" + } + ] + }, + "fractionalOp": { + "type": "array", + "minItems": 3, + "$comment": "there seems to be a bug here, where ajv gives a warning (not an error) because maxItems doesn't equal the number of entries in items, though this is valid in this case", + "items": [ + { + "description": "Bucketing value used in pseudorandom assignment; should be unique and stable for each subject of flag evaluation. Defaults to a concatenation of the flagKey and targetingKey.", + "$ref": "#/definitions/anyRule" + }, + { + "$ref": "#/definitions/fractionalWeightArg" + }, + { + "$ref": "#/definitions/fractionalWeightArg" + } + ], + "additionalItems": { + "$ref": "#/definitions/fractionalWeightArg" + } + }, + "fractionalShorthandOp": { + "type": "array", + "minItems": 2, + "items": { + "$ref": "#/definitions/fractionalWeightArg" + } + }, + "fractionalRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "fractional": { + "title": "Fractional Operation", + "description": "Deterministic, pseudorandom fractional distribution.", + "oneOf": [ + { + "$ref": "#/definitions/fractionalOp" + }, + { + "$ref": "#/definitions/fractionalShorthandOp" + } + ] + } + } + }, + "reference": { + "additionalProperties": false, + "type": "object", + "$comment": "patternProperties here is a bit of a hack to prevent this definition from being dereferenced early.", + "patternProperties": { + "^\\$ref$": { + "title": "Reference", + "description": "A reference to another entity, used for $evaluators (shared rules).", + "type": "string" + } + } + }, + "args": { + "oneOf": [ + { + "$ref": "#/definitions/reference" + }, + { + "$ref": "#/definitions/anyRule" + }, + { + "$ref": "#/definitions/primitive" + } + ] + }, + "anyRule": { + "anyOf": [ + { + "$ref": "#/definitions/varRule" + }, + { + "$ref": "#/definitions/missingRule" + }, + { + "$ref": "#/definitions/missingSomeRule" + }, + { + "$ref": "#/definitions/binaryRule" + }, + { + "$ref": "#/definitions/binaryOrTernaryRule" + }, + { + "$ref": "#/definitions/associativeRule" + }, + { + "$ref": "#/definitions/unaryRule" + }, + { + "$ref": "#/definitions/variadicRule" + }, + { + "$ref": "#/definitions/reduceRule" + }, + { + "$ref": "#/definitions/stringCompareRule" + }, + { + "$ref": "#/definitions/ruleSemVer" + }, + { + "$ref": "#/definitions/fractionalRule" + } + ] + } + } +} diff --git a/providers/statsig/pom.xml b/providers/statsig/pom.xml index 12ccea0e7..8a55ac548 100644 --- a/providers/statsig/pom.xml +++ b/providers/statsig/pom.xml @@ -20,7 +20,7 @@ com.statsig serversdk - 1.22.0 + 1.18.1