11import type { EvaluationContext , EvaluationContextValue } from '@openfeature/core'
22import { encodeUtf8 } from '../utf8'
3- import { compareSemver , compileRegex , isValidSemver } from './condition-helpers'
3+ import { compileRegex } from './condition-helpers'
4+ import { compareSemver , parseSemver } from './semver'
45import { sha256Hex } from './sha256'
56
67export type ConditionValueType = EvaluationContextValue | EvaluationContextValue [ ]
@@ -17,8 +18,8 @@ export enum OperatorType {
1718 ONE_OF_SHA256 = 'ONE_OF_SHA256' ,
1819 NOT_ONE_OF_SHA256 = 'NOT_ONE_OF_SHA256' ,
1920 IS_NULL = 'IS_NULL' ,
20- SEMVER_EQUAL = 'SEMVER_EQUAL ' ,
21- SEMVER_NOT_EQUAL = 'SEMVER_NOT_EQUAL ' ,
21+ SEMVER_EQ = 'SEMVER_EQ ' ,
22+ SEMVER_NEQ = 'SEMVER_NEQ ' ,
2223 SEMVER_LT = 'SEMVER_LT' ,
2324 SEMVER_LTE = 'SEMVER_LTE' ,
2425 SEMVER_GT = 'SEMVER_GT' ,
@@ -74,14 +75,16 @@ type Sha256Condition = {
7475 }
7576}
7677
78+ type SemverOperator =
79+ | OperatorType . SEMVER_EQ
80+ | OperatorType . SEMVER_NEQ
81+ | OperatorType . SEMVER_LT
82+ | OperatorType . SEMVER_LTE
83+ | OperatorType . SEMVER_GT
84+ | OperatorType . SEMVER_GTE
85+
7786type SemverCondition = {
78- operator :
79- | OperatorType . SEMVER_EQUAL
80- | OperatorType . SEMVER_NOT_EQUAL
81- | OperatorType . SEMVER_LT
82- | OperatorType . SEMVER_LTE
83- | OperatorType . SEMVER_GT
84- | OperatorType . SEMVER_GTE
87+ operator : SemverOperator
8588 attribute : string
8689 value : string
8790}
@@ -109,13 +112,8 @@ export function isValidRule(rule: Rule): boolean {
109112 if ( ! supportedOperators . has ( condition . operator ) ) {
110113 return false
111114 }
112- if ( condition . operator === OperatorType . MATCHES || condition . operator === OperatorType . NOT_MATCHES ) {
113- try {
114- compileRegex ( condition . value )
115- return true
116- } catch {
117- return false
118- }
115+ if ( isSemverOperator ( condition . operator ) ) {
116+ return parseSemver ( condition . value ) !== null
119117 }
120118 if ( condition . operator === OperatorType . ONE_OF_SHA256 || condition . operator === OperatorType . NOT_ONE_OF_SHA256 ) {
121119 return (
@@ -124,8 +122,13 @@ export function isValidRule(rule: Rule): boolean {
124122 condition . value . hashes . every ( ( hash ) => / ^ [ 0 - 9 a - f ] { 64 } $ / . test ( hash ) )
125123 )
126124 }
127- if ( condition . operator . startsWith ( 'SEMVER_' ) ) {
128- return isValidSemver ( condition . value as string )
125+ if ( condition . operator === OperatorType . MATCHES || condition . operator === OperatorType . NOT_MATCHES ) {
126+ try {
127+ compileRegex ( condition . value )
128+ return true
129+ } catch {
130+ return false
131+ }
129132 }
130133 return true
131134 } )
@@ -185,26 +188,66 @@ function evaluateCondition(subjectAttributes: EvaluationContext, condition: Cond
185188 const included = condition . value . hashes . includes ( sha256Hex ( input ) )
186189 return condition . operator === OperatorType . ONE_OF_SHA256 ? included : ! included
187190 }
188- case OperatorType . SEMVER_EQUAL :
189- case OperatorType . SEMVER_NOT_EQUAL :
191+ case OperatorType . SEMVER_EQ :
192+ case OperatorType . SEMVER_NEQ :
190193 case OperatorType . SEMVER_LT :
191194 case OperatorType . SEMVER_LTE :
192195 case OperatorType . SEMVER_GT :
193- case OperatorType . SEMVER_GTE : {
194- const comparison = compareSemver ( String ( value ) , condition . value )
195- if ( comparison === undefined ) return false
196- if ( condition . operator === OperatorType . SEMVER_EQUAL ) return comparison === 0
197- if ( condition . operator === OperatorType . SEMVER_NOT_EQUAL ) return comparison !== 0
198- if ( condition . operator === OperatorType . SEMVER_LT ) return comparison < 0
199- if ( condition . operator === OperatorType . SEMVER_LTE ) return comparison <= 0
200- if ( condition . operator === OperatorType . SEMVER_GT ) return comparison > 0
201- return comparison >= 0
202- }
196+ case OperatorType . SEMVER_GTE :
197+ return evaluateSemverCondition ( value , condition . value , condition . operator )
203198 }
204199 }
205200 return false
206201}
207202
203+ export function isSemverOperator ( operator : string ) : operator is SemverOperator {
204+ return (
205+ operator === OperatorType . SEMVER_EQ ||
206+ operator === OperatorType . SEMVER_NEQ ||
207+ operator === OperatorType . SEMVER_LT ||
208+ operator === OperatorType . SEMVER_LTE ||
209+ operator === OperatorType . SEMVER_GT ||
210+ operator === OperatorType . SEMVER_GTE
211+ )
212+ }
213+
214+ export function hasInvalidSemverComparand ( rule : Rule ) : boolean {
215+ return rule . conditions . some (
216+ ( condition ) => isSemverOperator ( condition . operator ) && parseSemver ( condition . value ) === null
217+ )
218+ }
219+
220+ function evaluateSemverCondition (
221+ attributeValue : EvaluationContextValue ,
222+ comparandValue : string ,
223+ operator : SemverOperator
224+ ) : boolean {
225+ if ( typeof attributeValue !== 'string' ) {
226+ return false
227+ }
228+
229+ const attribute = parseSemver ( attributeValue )
230+ const comparand = parseSemver ( comparandValue )
231+ if ( ! attribute || ! comparand ) {
232+ return false
233+ }
234+
235+ const ordering = compareSemver ( attribute , comparand )
236+ switch ( operator ) {
237+ case OperatorType . SEMVER_EQ :
238+ return ordering === 0
239+ case OperatorType . SEMVER_NEQ :
240+ return ordering !== 0
241+ case OperatorType . SEMVER_LT :
242+ return ordering < 0
243+ case OperatorType . SEMVER_LTE :
244+ return ordering <= 0
245+ case OperatorType . SEMVER_GT :
246+ return ordering > 0
247+ case OperatorType . SEMVER_GTE :
248+ return ordering >= 0
249+ }
250+ }
208251function isOneOf ( attributeValue : string , conditionValues : string [ ] ) {
209252 return conditionValues . includes ( attributeValue )
210253}
0 commit comments