Skip to content

Commit

Permalink
Allow null as a constant
Browse files Browse the repository at this point in the history
  • Loading branch information
bkeepers committed Jul 17, 2023
1 parent 82cf6ac commit 8152bec
Show file tree
Hide file tree
Showing 12 changed files with 58 additions and 21 deletions.
2 changes: 1 addition & 1 deletion lib/flipper/expression.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def self.build(object)
args = args.is_a?(Hash) ? [args] : Array(args)

new(name, args.map { |o| build(o) })
when String, Numeric, FalseClass, TrueClass
when String, Numeric, FalseClass, TrueClass, nil
Expression::Constant.new(object)
when Symbol
Expression::Constant.new(object.to_s)
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/expressions/percentage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Flipper
module Expressions
class Percentage
def self.call(value)
value.to_f.clamp(0, 100)
value.clamp(0, 100)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/gates/expression.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def open?(context)
end

def protects?(thing)
thing.is_a?(Flipper::Expression) || thing.is_a?(Hash)
thing.is_a?(Flipper::Expression) || thing.is_a?(Flipper::Expression::Constant) || thing.is_a?(Hash)
end

def wrap(thing)
Expand Down
8 changes: 8 additions & 0 deletions packages/expressions/examples/Equal.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
"expression": { "Equal": ["a", "a"] },
"result": { "enum": [true] }
},
{
"expression": { "Equal": [null, null] },
"result": { "enum": [true] }
},
{
"expression": { "Equal": [null, false] },
"result": { "enum": [false] }
},
{
"expression": { "Equal": [1, 2] },
"result": { "enum": [false] }
Expand Down
5 changes: 4 additions & 1 deletion packages/expressions/examples/expressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@
{
"expression": 1.1,
"result": { "enum": [1.1] }
},
{
"expression": null,
"result": { "enum": [null] }
}
],
"invalid": [
null,
{},
[]
]
Expand Down
2 changes: 1 addition & 1 deletion packages/expressions/lib/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Schema } from './schemas'
//
// Implements the same interface as Expression
export class Constant {
constructor (value, { id = uuidv4(), schema = Schema.resolve('#/definitions/constant') } = {}) {
constructor (value, { id = uuidv4(), schema = Schema.resolve('#') } = {}) {
this.value = value
this.id = id
this.schema = schema
Expand Down
16 changes: 6 additions & 10 deletions packages/expressions/lib/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ import { Constant } from './constant'
import { Schema } from './schemas'

function toArray (arg) {
if (Array.isArray(arg)) {
return arg
} else if (arg === null) {
return []
} else {
return [arg]
}
if (Array.isArray(arg)) return arg
if (arg === null) return []
return [arg]
}

// Simple model to transform this: `{ All: [{ Boolean: [true] }]`
Expand All @@ -20,14 +16,14 @@ export class Expression {
return expression
}

if (typeof expression === 'object') {
if (['number', 'string', 'boolean'].includes(typeof expression) || expression === null) {
return new Constant(expression, { schema })
} else if (typeof expression === 'object') {
if (Object.keys(expression).length !== 1) {
throw new TypeError(`Invalid expression: ${JSON.stringify(expression)}`)
}
const name = Object.keys(expression)[0]
return new Expression({ name, args: expression[name] })
} else if (['number', 'string', 'boolean'].includes(typeof expression)) {
return new Constant(expression, { schema })
} else {
throw new TypeError(`Invalid expression: ${JSON.stringify(expression)}`)
}
Expand Down
3 changes: 3 additions & 0 deletions packages/expressions/schemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"title": "Boolean",
"type": "boolean",
"enum": [true, false]
},
{
"type": "null"
}
]
},
Expand Down
5 changes: 2 additions & 3 deletions packages/expressions/test/constant.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Constant, Schema } from '../lib'

describe('Constant', () => {
describe('schema', () => {
test('defaults to Constant schema', () => {
expect(new Constant('string').schema.title).toEqual('Constant')
test('defaults to expression schema', () => {
expect(new Constant('string').schema.title).toEqual('Expression')
})

test('uses provided schema', () => {
Expand All @@ -26,7 +26,6 @@ describe('Constant', () => {

test('returns false for invalid value', () => {
expect(new Constant(['array']).validate().valid).toBe(false)
expect(new Constant({Now: []}).validate().valid).toBe(false)
})

test('uses provided schema', () => {
Expand Down
25 changes: 24 additions & 1 deletion packages/expressions/test/expression.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ describe('Expression', () => {
expect(expression.value).toEqual({ All: [true] })
})

test('builds an expression from a boolean constant', () => {
const expression = Expression.build(true)
expect(expression).toBeInstanceOf(Constant)
expect(expression.value).toEqual(true)
})

test('builds an expression from a string constant', () => {
const expression = Expression.build("hello")
expect(expression).toBeInstanceOf(Constant)
expect(expression.value).toEqual("hello")
})

test('builds an expression from a null constant', () => {
const expression = Expression.build(null)
expect(expression).toBeInstanceOf(Constant)
expect(expression.value).toEqual(null)
})

test('throws error on invalid expression', () => {
expect(() => Expression.build([])).toThrowError(TypeError)
expect(() => Expression.build(new Date())).toThrowError(TypeError)
Expand All @@ -25,8 +43,13 @@ describe('Expression', () => {
expect(expression.args[1].schema).toEqual(schema.items[1])
})

test('sets schema for constant', () => {
const expression = Expression.build(false)
expect(expression.schema.$id).toEqual(Schema.resolve('#').$id)
})

test('each subexpression uses its own schema', () => {
const expression = Expression.build({ GreaterThan: [ { Now: [] }, { Property: ['released_at'] } ] })
const expression = Expression.build({ GreaterThan: [{ Now: [] }, { Property: ['released_at'] }] })
expect(expression.schema).toEqual(Schema.resolve('GreaterThan.schema.json'))
expect(expression.args[0].schema).toEqual(Schema.resolve('Now.schema.json'))
expect(expression.args[1].schema).toEqual(Schema.resolve('Property.schema.json'))
Expand Down
4 changes: 2 additions & 2 deletions packages/expressions/test/schemas.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ describe('schema.json', () => {
describe('resolveAnyOf', () => {
test('returns nested anyOf', () => {
const ref = Schema.resolve('#')
expect(ref.resolveAnyOf()).toHaveLength(4)
expect(ref.resolveAnyOf()).toHaveLength(5)
})

test('returns array of schemas', () => {
const ref = Schema.resolve('#/definitions/constant')
expect(ref.resolveAnyOf()).toHaveLength(3)
expect(ref.resolveAnyOf()).toHaveLength(4)
expect(ref.resolveAnyOf()).toEqual(ref.anyOf)
})
})
Expand Down
5 changes: 5 additions & 0 deletions spec/flipper/gates/expression_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ def context(expression, properties: {})
expect(subject.protects?(expression)).to be(true)
end

it 'returns true for Flipper::Constant' do
expression = Flipper.boolean(true)
expect(subject.protects?(expression)).to be(true)
end

it 'returns true for Hash' do
expression = Flipper.number(20).eq(20)
expect(subject.protects?(expression.value)).to be(true)
Expand Down

0 comments on commit 8152bec

Please sign in to comment.