mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	open api implementation - client features controller (#1745)
* open api implementation - client features controller * open api implementation - client features controller * bug fix * test fix * PR comments * OAS for client-api metrics.ts * Refactoring * Refactoring * bug fix * fix PR comments * PR comment * PR comment
This commit is contained in:
		
							parent
							
								
									b67aca8fbf
								
							
						
					
					
						commit
						e875e67d24
					
				@ -82,10 +82,14 @@ import { emailSchema } from './spec/email-schema';
 | 
			
		||||
import { strategySchema } from './spec/strategy-schema';
 | 
			
		||||
import { strategiesSchema } from './spec/strategies-schema';
 | 
			
		||||
import { upsertStrategySchema } from './spec/upsert-strategy-schema';
 | 
			
		||||
import { clientFeaturesQuerySchema } from './spec/client-features-query-schema';
 | 
			
		||||
import { clientFeatureSchema } from './spec/client-feature-schema';
 | 
			
		||||
import { clientFeaturesSchema } from './spec/client-features-schema';
 | 
			
		||||
import { eventSchema } from './spec/event-schema';
 | 
			
		||||
import { eventsSchema } from './spec/events-schema';
 | 
			
		||||
import { featureEventsSchema } from './spec/feature-events-schema';
 | 
			
		||||
import { clientApplicationSchema } from './spec/client-application-schema';
 | 
			
		||||
import { clientVariantSchema } from './spec/client-variant-schema';
 | 
			
		||||
import { IServerOption } from '../types';
 | 
			
		||||
import { URL } from 'url';
 | 
			
		||||
 | 
			
		||||
@ -101,6 +105,10 @@ export const schemas = {
 | 
			
		||||
    applicationsSchema,
 | 
			
		||||
    clientApplicationSchema,
 | 
			
		||||
    cloneFeatureSchema,
 | 
			
		||||
    clientFeatureSchema,
 | 
			
		||||
    clientFeaturesSchema,
 | 
			
		||||
    clientVariantSchema,
 | 
			
		||||
    clientFeaturesQuerySchema,
 | 
			
		||||
    changePasswordSchema,
 | 
			
		||||
    constraintSchema,
 | 
			
		||||
    contextFieldSchema,
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
			
		||||
 | 
			
		||||
exports[`clientFeaturesSchema no fields 1`] = `
 | 
			
		||||
Object {
 | 
			
		||||
  "errors": Array [
 | 
			
		||||
    Object {
 | 
			
		||||
      "instancePath": "",
 | 
			
		||||
      "keyword": "required",
 | 
			
		||||
      "message": "must have required property 'version'",
 | 
			
		||||
      "params": Object {
 | 
			
		||||
        "missingProperty": "version",
 | 
			
		||||
      },
 | 
			
		||||
      "schemaPath": "#/required",
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  "schema": "#/components/schemas/clientFeaturesSchema",
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
@ -4,13 +4,13 @@ exports[`featureSchema constraints 1`] = `
 | 
			
		||||
Object {
 | 
			
		||||
  "errors": Array [
 | 
			
		||||
    Object {
 | 
			
		||||
      "instancePath": "/strategies/0",
 | 
			
		||||
      "instancePath": "/strategies/0/constraints/0",
 | 
			
		||||
      "keyword": "required",
 | 
			
		||||
      "message": "must have required property 'id'",
 | 
			
		||||
      "message": "must have required property 'operator'",
 | 
			
		||||
      "params": Object {
 | 
			
		||||
        "missingProperty": "id",
 | 
			
		||||
        "missingProperty": "operator",
 | 
			
		||||
      },
 | 
			
		||||
      "schemaPath": "#/required",
 | 
			
		||||
      "schemaPath": "#/components/schemas/constraintSchema/required",
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  "schema": "#/components/schemas/featureSchema",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										70
									
								
								src/lib/openapi/spec/client-feature-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/lib/openapi/spec/client-feature-schema.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
import { FromSchema } from 'json-schema-to-ts';
 | 
			
		||||
import { constraintSchema } from './constraint-schema';
 | 
			
		||||
import { parametersSchema } from './parameters-schema';
 | 
			
		||||
import { featureStrategySchema } from './feature-strategy-schema';
 | 
			
		||||
import { clientVariantSchema } from './client-variant-schema';
 | 
			
		||||
 | 
			
		||||
export const clientFeatureSchema = {
 | 
			
		||||
    $id: '#/components/schemas/clientFeatureSchema',
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    required: ['name', 'enabled'],
 | 
			
		||||
    additionalProperties: false,
 | 
			
		||||
    properties: {
 | 
			
		||||
        name: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
        },
 | 
			
		||||
        type: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
        },
 | 
			
		||||
        description: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            nullable: true,
 | 
			
		||||
        },
 | 
			
		||||
        createdAt: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            format: 'date-time',
 | 
			
		||||
            nullable: true,
 | 
			
		||||
        },
 | 
			
		||||
        lastSeenAt: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            format: 'date-time',
 | 
			
		||||
            nullable: true,
 | 
			
		||||
        },
 | 
			
		||||
        enabled: {
 | 
			
		||||
            type: 'boolean',
 | 
			
		||||
        },
 | 
			
		||||
        stale: {
 | 
			
		||||
            type: 'boolean',
 | 
			
		||||
        },
 | 
			
		||||
        impressionData: {
 | 
			
		||||
            type: 'boolean',
 | 
			
		||||
            nullable: true,
 | 
			
		||||
        },
 | 
			
		||||
        project: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
        },
 | 
			
		||||
        strategies: {
 | 
			
		||||
            type: 'array',
 | 
			
		||||
            items: {
 | 
			
		||||
                $ref: '#/components/schemas/featureStrategySchema',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        variants: {
 | 
			
		||||
            type: 'array',
 | 
			
		||||
            items: {
 | 
			
		||||
                $ref: '#/components/schemas/clientVariantSchema',
 | 
			
		||||
            },
 | 
			
		||||
            nullable: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
        schemas: {
 | 
			
		||||
            constraintSchema,
 | 
			
		||||
            parametersSchema,
 | 
			
		||||
            featureStrategySchema,
 | 
			
		||||
            clientVariantSchema,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
export type ClientFeatureSchema = FromSchema<typeof clientFeatureSchema>;
 | 
			
		||||
							
								
								
									
										24
									
								
								src/lib/openapi/spec/client-features-query-schema.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/lib/openapi/spec/client-features-query-schema.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
import { validateSchema } from '../validate';
 | 
			
		||||
import { ClientFeaturesQuerySchema } from './client-features-query-schema';
 | 
			
		||||
 | 
			
		||||
test('clientFeatureQuerySchema empty', () => {
 | 
			
		||||
    const data: ClientFeaturesQuerySchema = {};
 | 
			
		||||
 | 
			
		||||
    expect(
 | 
			
		||||
        validateSchema('#/components/schemas/clientFeaturesQuerySchema', data),
 | 
			
		||||
    ).toBeUndefined();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('clientFeatureQuerySchema all fields', () => {
 | 
			
		||||
    const data: ClientFeaturesQuerySchema = {
 | 
			
		||||
        tag: [['some-tag', 'some-other-tag']],
 | 
			
		||||
        project: ['default'],
 | 
			
		||||
        namePrefix: 'some-prefix',
 | 
			
		||||
        environment: 'some-env',
 | 
			
		||||
        inlineSegmentConstraints: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    expect(
 | 
			
		||||
        validateSchema('#/components/schemas/clientFeaturesQuerySchema', data),
 | 
			
		||||
    ).toBeUndefined();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										39
									
								
								src/lib/openapi/spec/client-features-query-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/lib/openapi/spec/client-features-query-schema.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
import { FromSchema } from 'json-schema-to-ts';
 | 
			
		||||
 | 
			
		||||
export const clientFeaturesQuerySchema = {
 | 
			
		||||
    $id: '#/components/schemas/clientFeaturesQuerySchema',
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    required: [],
 | 
			
		||||
    additionalProperties: false,
 | 
			
		||||
    properties: {
 | 
			
		||||
        tag: {
 | 
			
		||||
            type: 'array',
 | 
			
		||||
            items: {
 | 
			
		||||
                type: 'array',
 | 
			
		||||
                items: {
 | 
			
		||||
                    type: 'string',
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        project: {
 | 
			
		||||
            type: 'array',
 | 
			
		||||
            items: {
 | 
			
		||||
                type: 'string',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        namePrefix: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
        },
 | 
			
		||||
        environment: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
        },
 | 
			
		||||
        inlineSegmentConstraints: {
 | 
			
		||||
            type: 'boolean',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    components: {},
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
export type ClientFeaturesQuerySchema = FromSchema<
 | 
			
		||||
    typeof clientFeaturesQuerySchema
 | 
			
		||||
>;
 | 
			
		||||
							
								
								
									
										395
									
								
								src/lib/openapi/spec/client-features-schema.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										395
									
								
								src/lib/openapi/spec/client-features-schema.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,395 @@
 | 
			
		||||
import { validateSchema } from '../validate';
 | 
			
		||||
import { ClientFeaturesSchema } from './client-features-schema';
 | 
			
		||||
 | 
			
		||||
test('clientFeaturesSchema no fields', () => {
 | 
			
		||||
    expect(
 | 
			
		||||
        validateSchema('#/components/schemas/clientFeaturesSchema', {}),
 | 
			
		||||
    ).toMatchSnapshot();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('clientFeaturesSchema required fields', () => {
 | 
			
		||||
    const data: ClientFeaturesSchema = {
 | 
			
		||||
        version: 0,
 | 
			
		||||
        query: {},
 | 
			
		||||
        features: [
 | 
			
		||||
            {
 | 
			
		||||
                name: 'some-name',
 | 
			
		||||
                enabled: false,
 | 
			
		||||
                impressionData: false,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    expect(
 | 
			
		||||
        validateSchema('#/components/schemas/clientFeaturesSchema', data),
 | 
			
		||||
    ).toBeUndefined();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('clientFeaturesSchema java-sdk expected response', () => {
 | 
			
		||||
    const json = `{
 | 
			
		||||
    "version": 2,
 | 
			
		||||
    "segments": [
 | 
			
		||||
        {
 | 
			
		||||
            "id": 1,
 | 
			
		||||
            "name": "some-name",
 | 
			
		||||
            "description": null,
 | 
			
		||||
            "constraints": [
 | 
			
		||||
                {
 | 
			
		||||
                    "contextName": "some-name",
 | 
			
		||||
                    "operator": "IN",
 | 
			
		||||
                    "value": "name",
 | 
			
		||||
                    "inverted": false,
 | 
			
		||||
                    "caseInsensitive": true
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "features": [
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Test.old",
 | 
			
		||||
                "description": "No variants here!",
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "default"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "variants": null,
 | 
			
		||||
                "createdAt": "2019-01-24T10:38:10.370Z"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Test.variants",
 | 
			
		||||
                "description": null,
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "default",
 | 
			
		||||
                        "segments": [
 | 
			
		||||
                            1
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "variants": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "variant1",
 | 
			
		||||
                        "weight": 50
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "variant2",
 | 
			
		||||
                        "weight": 50
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "createdAt": "2019-01-24T10:41:45.236Z"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "featureX",
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "default"
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "featureY",
 | 
			
		||||
                "enabled": false,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "baz",
 | 
			
		||||
                        "parameters": {
 | 
			
		||||
                            "foo": "bar"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "featureZ",
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "default"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "hola",
 | 
			
		||||
                        "parameters": {
 | 
			
		||||
                            "name": "val"
 | 
			
		||||
                        },
 | 
			
		||||
                        "segments": [1]
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
    expect(
 | 
			
		||||
        validateSchema(
 | 
			
		||||
            '#/components/schemas/clientFeaturesSchema',
 | 
			
		||||
            JSON.parse(json),
 | 
			
		||||
        ),
 | 
			
		||||
    ).toBeUndefined();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('clientFeaturesSchema unleash-proxy expected response', () => {
 | 
			
		||||
    const json = `{
 | 
			
		||||
    "version": 2,
 | 
			
		||||
    "segments": [
 | 
			
		||||
        {
 | 
			
		||||
            "id": 1,
 | 
			
		||||
            "name": "some-name",
 | 
			
		||||
            "description": null,
 | 
			
		||||
            "constraints": [
 | 
			
		||||
                {
 | 
			
		||||
                    "contextName": "some-name",
 | 
			
		||||
                    "operator": "IN",
 | 
			
		||||
                    "value": "name",
 | 
			
		||||
                    "inverted": false,
 | 
			
		||||
                    "caseInsensitive": true
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "features": [
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Test.old",
 | 
			
		||||
                "description": "No variants here!",
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "default"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "variants": null,
 | 
			
		||||
                "createdAt": "2019-01-24T10:38:10.370Z"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Test.variants",
 | 
			
		||||
                "description": null,
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "default",
 | 
			
		||||
                        "segments": [
 | 
			
		||||
                            1
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "variants": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "variant1",
 | 
			
		||||
                        "weight": 50
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "variant2",
 | 
			
		||||
                        "weight": 50
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "createdAt": "2019-01-24T10:41:45.236Z"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "featureX",
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "default"
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "featureY",
 | 
			
		||||
                "enabled": false,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "baz",
 | 
			
		||||
                        "parameters": {
 | 
			
		||||
                            "foo": "bar"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "featureZ",
 | 
			
		||||
                "enabled": true,
 | 
			
		||||
                "strategies": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "default"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "hola",
 | 
			
		||||
                        "parameters": {
 | 
			
		||||
                            "name": "val"
 | 
			
		||||
                        },
 | 
			
		||||
                        "segments": [1]
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
    expect(
 | 
			
		||||
        validateSchema(
 | 
			
		||||
            '#/components/schemas/clientFeaturesSchema',
 | 
			
		||||
            JSON.parse(json),
 | 
			
		||||
        ),
 | 
			
		||||
    ).toBeUndefined();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('clientFeaturesSchema client specification test 15', () => {
 | 
			
		||||
    const json = `{
 | 
			
		||||
       "version": 2,
 | 
			
		||||
        "features": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "F9.globalSegmentOn",
 | 
			
		||||
            "description": "With global segment referencing constraint in on state",
 | 
			
		||||
            "enabled": true,
 | 
			
		||||
            "strategies": [
 | 
			
		||||
              {
 | 
			
		||||
                "name": "default",
 | 
			
		||||
                "parameters": {},
 | 
			
		||||
                "segments": [1]
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "F9.globalSegmentOff",
 | 
			
		||||
            "description": "With global segment referencing constraint in off state",
 | 
			
		||||
            "enabled": true,
 | 
			
		||||
            "strategies": [
 | 
			
		||||
              {
 | 
			
		||||
                "name": "default",
 | 
			
		||||
                "parameters": {},
 | 
			
		||||
                "segments": [2]
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "F9.globalSegmentAndConstraint",
 | 
			
		||||
            "description": "With global segment and constraint both on",
 | 
			
		||||
            "enabled": true,
 | 
			
		||||
            "strategies": [
 | 
			
		||||
              {
 | 
			
		||||
                "name": "default",
 | 
			
		||||
                "parameters": {},
 | 
			
		||||
                "constraints": [
 | 
			
		||||
                  {
 | 
			
		||||
                    "contextName": "version",
 | 
			
		||||
                    "operator": "SEMVER_EQ",
 | 
			
		||||
                    "value": "1.2.2"
 | 
			
		||||
                  }
 | 
			
		||||
                ],
 | 
			
		||||
                "segments": [1]
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "F9.withExtraParams",
 | 
			
		||||
            "description": "With global segment that doesn't exist",
 | 
			
		||||
            "enabled": true,
 | 
			
		||||
            "project": "some-project",
 | 
			
		||||
            "strategies": [
 | 
			
		||||
              {
 | 
			
		||||
                "name": "default",
 | 
			
		||||
                "parameters": {},
 | 
			
		||||
                "constraints": [
 | 
			
		||||
                  {
 | 
			
		||||
                    "contextName": "version",
 | 
			
		||||
                    "operator": "SEMVER_EQ",
 | 
			
		||||
                    "value": "1.2.2"
 | 
			
		||||
                  }
 | 
			
		||||
                ],
 | 
			
		||||
                "segments": [3]
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "F9.withSeveralConstraintsAndSegments",
 | 
			
		||||
            "description": "With several segments and constraints",
 | 
			
		||||
            "enabled": true,
 | 
			
		||||
            "strategies": [
 | 
			
		||||
              {
 | 
			
		||||
                "name": "default",
 | 
			
		||||
                "parameters": {},
 | 
			
		||||
                "constraints": [
 | 
			
		||||
                  {
 | 
			
		||||
                    "contextName": "customNumber",
 | 
			
		||||
                    "operator": "NUM_LT",
 | 
			
		||||
                    "value": "10"
 | 
			
		||||
                  },
 | 
			
		||||
                  {
 | 
			
		||||
                    "contextName": "version",
 | 
			
		||||
                    "operator": "SEMVER_LT",
 | 
			
		||||
                    "value": "3.2.2"
 | 
			
		||||
                  }
 | 
			
		||||
                ],
 | 
			
		||||
                "segments": [1, 4, 5]
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "segments": [
 | 
			
		||||
          {
 | 
			
		||||
            "id": 1,
 | 
			
		||||
            "constraints": [
 | 
			
		||||
              {
 | 
			
		||||
                "contextName": "version",
 | 
			
		||||
                "operator": "SEMVER_EQ",
 | 
			
		||||
                "value": "1.2.2"
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "id": 2,
 | 
			
		||||
            "constraints": [
 | 
			
		||||
              {
 | 
			
		||||
                "contextName": "version",
 | 
			
		||||
                "operator": "SEMVER_EQ",
 | 
			
		||||
                "value": "3.1.4"
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "id": 3,
 | 
			
		||||
            "constraints": [
 | 
			
		||||
              {
 | 
			
		||||
                "contextName": "version",
 | 
			
		||||
                "operator": "SEMVER_EQ",
 | 
			
		||||
                "value": "3.1.4"
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "id": 4,
 | 
			
		||||
            "constraints": [
 | 
			
		||||
              {
 | 
			
		||||
                "contextName": "customName",
 | 
			
		||||
                "operator": "STR_CONTAINS",
 | 
			
		||||
                "values": ["Pi"]
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "id": 5,
 | 
			
		||||
            "constraints": [
 | 
			
		||||
              {
 | 
			
		||||
                "contextName": "slicesLeft",
 | 
			
		||||
                "operator": "NUM_LTE",
 | 
			
		||||
                "value": "4"
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
    expect(
 | 
			
		||||
        validateSchema(
 | 
			
		||||
            '#/components/schemas/clientFeaturesSchema',
 | 
			
		||||
            JSON.parse(json),
 | 
			
		||||
        ),
 | 
			
		||||
    ).toBeUndefined();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										51
									
								
								src/lib/openapi/spec/client-features-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/lib/openapi/spec/client-features-schema.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
import { FromSchema } from 'json-schema-to-ts';
 | 
			
		||||
import { clientFeaturesQuerySchema } from './client-features-query-schema';
 | 
			
		||||
import { segmentSchema } from './segment-schema';
 | 
			
		||||
import { constraintSchema } from './constraint-schema';
 | 
			
		||||
import { environmentSchema } from './environment-schema';
 | 
			
		||||
import { overrideSchema } from './override-schema';
 | 
			
		||||
import { parametersSchema } from './parameters-schema';
 | 
			
		||||
import { featureStrategySchema } from './feature-strategy-schema';
 | 
			
		||||
import { variantSchema } from './variant-schema';
 | 
			
		||||
import { clientFeatureSchema } from './client-feature-schema';
 | 
			
		||||
 | 
			
		||||
export const clientFeaturesSchema = {
 | 
			
		||||
    $id: '#/components/schemas/clientFeaturesSchema',
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    required: ['version', 'features'],
 | 
			
		||||
    properties: {
 | 
			
		||||
        version: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
        },
 | 
			
		||||
        features: {
 | 
			
		||||
            type: 'array',
 | 
			
		||||
            items: {
 | 
			
		||||
                $ref: '#/components/schemas/clientFeatureSchema',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        segments: {
 | 
			
		||||
            type: 'array',
 | 
			
		||||
            items: {
 | 
			
		||||
                $ref: '#/components/schemas/segmentSchema',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        query: {
 | 
			
		||||
            $ref: '#/components/schemas/clientFeaturesQuerySchema',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
        schemas: {
 | 
			
		||||
            constraintSchema,
 | 
			
		||||
            clientFeatureSchema,
 | 
			
		||||
            environmentSchema,
 | 
			
		||||
            segmentSchema,
 | 
			
		||||
            clientFeaturesQuerySchema,
 | 
			
		||||
            overrideSchema,
 | 
			
		||||
            parametersSchema,
 | 
			
		||||
            featureStrategySchema,
 | 
			
		||||
            variantSchema,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
export type ClientFeaturesSchema = FromSchema<typeof clientFeaturesSchema>;
 | 
			
		||||
							
								
								
									
										31
									
								
								src/lib/openapi/spec/client-variant-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/lib/openapi/spec/client-variant-schema.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
import { FromSchema } from 'json-schema-to-ts';
 | 
			
		||||
 | 
			
		||||
export const clientVariantSchema = {
 | 
			
		||||
    $id: '#/components/schemas/clientVariantSchema',
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    additionalProperties: false,
 | 
			
		||||
    required: ['name', 'weight'],
 | 
			
		||||
    properties: {
 | 
			
		||||
        name: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
        },
 | 
			
		||||
        weight: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
        },
 | 
			
		||||
        payload: {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            required: ['type', 'value'],
 | 
			
		||||
            properties: {
 | 
			
		||||
                type: {
 | 
			
		||||
                    type: 'string',
 | 
			
		||||
                },
 | 
			
		||||
                value: {
 | 
			
		||||
                    type: 'string',
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    components: {},
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
export type ClientVariantSchema = FromSchema<typeof clientVariantSchema>;
 | 
			
		||||
@ -6,7 +6,7 @@ export const featureStrategySchema = {
 | 
			
		||||
    $id: '#/components/schemas/featureStrategySchema',
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    additionalProperties: false,
 | 
			
		||||
    required: ['name', 'id'],
 | 
			
		||||
    required: ['name'],
 | 
			
		||||
    properties: {
 | 
			
		||||
        id: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
 | 
			
		||||
@ -5,13 +5,17 @@ export const segmentSchema = {
 | 
			
		||||
    $id: '#/components/schemas/segmentSchema',
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    additionalProperties: false,
 | 
			
		||||
    required: ['name', 'constraints'],
 | 
			
		||||
    required: ['id', 'constraints'],
 | 
			
		||||
    properties: {
 | 
			
		||||
        id: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
        },
 | 
			
		||||
        name: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
        },
 | 
			
		||||
        description: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            nullable: true,
 | 
			
		||||
        },
 | 
			
		||||
        constraints: {
 | 
			
		||||
            type: 'array',
 | 
			
		||||
 | 
			
		||||
@ -70,7 +70,10 @@ test('should get empty getFeatures via client', () => {
 | 
			
		||||
test('if caching is enabled should memoize', async () => {
 | 
			
		||||
    const getClientFeatures = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const getActive = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const respondWithValidation = jest.fn().mockReturnValue({});
 | 
			
		||||
    const validPath = jest.fn().mockReturnValue(jest.fn());
 | 
			
		||||
    const clientSpecService = new ClientSpecService({ getLogger });
 | 
			
		||||
    const openApiService = { respondWithValidation, validPath };
 | 
			
		||||
    const featureToggleServiceV2 = { getClientFeatures };
 | 
			
		||||
    const segmentService = { getActive };
 | 
			
		||||
 | 
			
		||||
@ -78,6 +81,8 @@ test('if caching is enabled should memoize', async () => {
 | 
			
		||||
        {
 | 
			
		||||
            clientSpecService,
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            openApiService,
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            featureToggleServiceV2,
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            segmentService,
 | 
			
		||||
@ -99,14 +104,19 @@ test('if caching is enabled should memoize', async () => {
 | 
			
		||||
test('if caching is not enabled all calls goes to service', async () => {
 | 
			
		||||
    const getClientFeatures = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const getActive = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const respondWithValidation = jest.fn().mockReturnValue({});
 | 
			
		||||
    const validPath = jest.fn().mockReturnValue(jest.fn());
 | 
			
		||||
    const clientSpecService = new ClientSpecService({ getLogger });
 | 
			
		||||
    const featureToggleServiceV2 = { getClientFeatures };
 | 
			
		||||
    const segmentService = { getActive };
 | 
			
		||||
    const openApiService = { respondWithValidation, validPath };
 | 
			
		||||
 | 
			
		||||
    const controller = new FeatureController(
 | 
			
		||||
        {
 | 
			
		||||
            clientSpecService,
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            openApiService,
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            featureToggleServiceV2,
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            segmentService,
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
import memoizee from 'memoizee';
 | 
			
		||||
import { Response } from 'express';
 | 
			
		||||
import Controller from '../controller';
 | 
			
		||||
import { IUnleashServices } from '../../types/services';
 | 
			
		||||
import { IUnleashConfig } from '../../types/option';
 | 
			
		||||
import { IUnleashConfig, IUnleashServices } from '../../types';
 | 
			
		||||
import FeatureToggleService from '../../services/feature-toggle-service';
 | 
			
		||||
import { Logger } from '../../logger';
 | 
			
		||||
import { querySchema } from '../../schema/feature-schema';
 | 
			
		||||
@ -14,6 +13,18 @@ import { ALL, isAllProjects } from '../../types/models/api-token';
 | 
			
		||||
import { SegmentService } from '../../services/segment-service';
 | 
			
		||||
import { FeatureConfigurationClient } from '../../types/stores/feature-strategies-store';
 | 
			
		||||
import { ClientSpecService } from '../../services/client-spec-service';
 | 
			
		||||
import { OpenApiService } from '../../services/openapi-service';
 | 
			
		||||
import { NONE } from '../../types/permissions';
 | 
			
		||||
import { createResponseSchema } from '../../openapi';
 | 
			
		||||
import { ClientFeaturesQuerySchema } from '../../openapi/spec/client-features-query-schema';
 | 
			
		||||
import {
 | 
			
		||||
    clientFeatureSchema,
 | 
			
		||||
    ClientFeatureSchema,
 | 
			
		||||
} from '../../openapi/spec/client-feature-schema';
 | 
			
		||||
import {
 | 
			
		||||
    clientFeaturesSchema,
 | 
			
		||||
    ClientFeaturesSchema,
 | 
			
		||||
} from '../../openapi/spec/client-features-schema';
 | 
			
		||||
 | 
			
		||||
const version = 2;
 | 
			
		||||
 | 
			
		||||
@ -31,6 +42,8 @@ export default class FeatureController extends Controller {
 | 
			
		||||
 | 
			
		||||
    private clientSpecService: ClientSpecService;
 | 
			
		||||
 | 
			
		||||
    private openApiService: OpenApiService;
 | 
			
		||||
 | 
			
		||||
    private readonly cache: boolean;
 | 
			
		||||
 | 
			
		||||
    private cachedFeatures: any;
 | 
			
		||||
@ -40,9 +53,13 @@ export default class FeatureController extends Controller {
 | 
			
		||||
            featureToggleServiceV2,
 | 
			
		||||
            segmentService,
 | 
			
		||||
            clientSpecService,
 | 
			
		||||
            openApiService,
 | 
			
		||||
        }: Pick<
 | 
			
		||||
            IUnleashServices,
 | 
			
		||||
            'featureToggleServiceV2' | 'segmentService' | 'clientSpecService'
 | 
			
		||||
            | 'featureToggleServiceV2'
 | 
			
		||||
            | 'segmentService'
 | 
			
		||||
            | 'clientSpecService'
 | 
			
		||||
            | 'openApiService'
 | 
			
		||||
        >,
 | 
			
		||||
        config: IUnleashConfig,
 | 
			
		||||
    ) {
 | 
			
		||||
@ -51,10 +68,40 @@ export default class FeatureController extends Controller {
 | 
			
		||||
        this.featureToggleServiceV2 = featureToggleServiceV2;
 | 
			
		||||
        this.segmentService = segmentService;
 | 
			
		||||
        this.clientSpecService = clientSpecService;
 | 
			
		||||
        this.openApiService = openApiService;
 | 
			
		||||
        this.logger = config.getLogger('client-api/feature.js');
 | 
			
		||||
 | 
			
		||||
        this.get('/', this.getAll);
 | 
			
		||||
        this.get('/:featureName', this.getFeatureToggle);
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'get',
 | 
			
		||||
            path: '/:featureName',
 | 
			
		||||
            handler: this.getFeatureToggle,
 | 
			
		||||
            permission: NONE,
 | 
			
		||||
            middleware: [
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    operationId: 'getClientFeature',
 | 
			
		||||
                    tags: ['client'],
 | 
			
		||||
                    responses: {
 | 
			
		||||
                        200: createResponseSchema('clientFeaturesSchema'),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'get',
 | 
			
		||||
            path: '',
 | 
			
		||||
            handler: this.getAll,
 | 
			
		||||
            permission: NONE,
 | 
			
		||||
            middleware: [
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    operationId: 'getAllClientFeatures',
 | 
			
		||||
                    tags: ['client'],
 | 
			
		||||
                    responses: {
 | 
			
		||||
                        200: createResponseSchema('clientFeaturesSchema'),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (clientFeatureCaching?.enabled) {
 | 
			
		||||
            this.cache = true;
 | 
			
		||||
@ -148,7 +195,10 @@ export default class FeatureController extends Controller {
 | 
			
		||||
        return query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getAll(req: IAuthRequest, res: Response): Promise<void> {
 | 
			
		||||
    async getAll(
 | 
			
		||||
        req: IAuthRequest,
 | 
			
		||||
        res: Response<ClientFeaturesSchema>,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const query = await this.resolveQuery(req);
 | 
			
		||||
 | 
			
		||||
        const [features, segments] = this.cache
 | 
			
		||||
@ -156,13 +206,26 @@ export default class FeatureController extends Controller {
 | 
			
		||||
            : await this.resolveFeaturesAndSegments(query);
 | 
			
		||||
 | 
			
		||||
        if (this.clientSpecService.requestSupportsSpec(req, 'segments')) {
 | 
			
		||||
            res.json({ version, features, query, segments });
 | 
			
		||||
            this.openApiService.respondWithValidation(
 | 
			
		||||
                200,
 | 
			
		||||
                res,
 | 
			
		||||
                clientFeaturesSchema.$id,
 | 
			
		||||
                { version, features, query: { ...query }, segments },
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            res.json({ version, features, query });
 | 
			
		||||
            this.openApiService.respondWithValidation(
 | 
			
		||||
                200,
 | 
			
		||||
                res,
 | 
			
		||||
                clientFeaturesSchema.$id,
 | 
			
		||||
                { version, features, query },
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getFeatureToggle(req: IAuthRequest, res: Response): Promise<void> {
 | 
			
		||||
    async getFeatureToggle(
 | 
			
		||||
        req: IAuthRequest<{ featureName: string }, ClientFeaturesQuerySchema>,
 | 
			
		||||
        res: Response<ClientFeatureSchema>,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const name = req.params.featureName;
 | 
			
		||||
        const featureQuery = await this.resolveQuery(req);
 | 
			
		||||
        const q = { ...featureQuery, namePrefix: name };
 | 
			
		||||
@ -172,6 +235,13 @@ export default class FeatureController extends Controller {
 | 
			
		||||
        if (!toggle) {
 | 
			
		||||
            throw new NotFoundError(`Could not find feature toggle ${name}`);
 | 
			
		||||
        }
 | 
			
		||||
        res.json(toggle).end();
 | 
			
		||||
        this.openApiService.respondWithValidation(
 | 
			
		||||
            200,
 | 
			
		||||
            res,
 | 
			
		||||
            clientFeatureSchema.$id,
 | 
			
		||||
            {
 | 
			
		||||
                ...toggle,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -352,6 +352,151 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "clientFeatureSchema": Object {
 | 
			
		||||
        "additionalProperties": false,
 | 
			
		||||
        "properties": Object {
 | 
			
		||||
          "createdAt": Object {
 | 
			
		||||
            "format": "date-time",
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "description": Object {
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "enabled": Object {
 | 
			
		||||
            "type": "boolean",
 | 
			
		||||
          },
 | 
			
		||||
          "impressionData": Object {
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "boolean",
 | 
			
		||||
          },
 | 
			
		||||
          "lastSeenAt": Object {
 | 
			
		||||
            "format": "date-time",
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "name": Object {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "project": Object {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "stale": Object {
 | 
			
		||||
            "type": "boolean",
 | 
			
		||||
          },
 | 
			
		||||
          "strategies": Object {
 | 
			
		||||
            "items": Object {
 | 
			
		||||
              "$ref": "#/components/schemas/featureStrategySchema",
 | 
			
		||||
            },
 | 
			
		||||
            "type": "array",
 | 
			
		||||
          },
 | 
			
		||||
          "type": Object {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "variants": Object {
 | 
			
		||||
            "items": Object {
 | 
			
		||||
              "$ref": "#/components/schemas/clientVariantSchema",
 | 
			
		||||
            },
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "array",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "required": Array [
 | 
			
		||||
          "name",
 | 
			
		||||
          "enabled",
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "clientFeaturesQuerySchema": Object {
 | 
			
		||||
        "additionalProperties": false,
 | 
			
		||||
        "properties": Object {
 | 
			
		||||
          "environment": Object {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "inlineSegmentConstraints": Object {
 | 
			
		||||
            "type": "boolean",
 | 
			
		||||
          },
 | 
			
		||||
          "namePrefix": Object {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "project": Object {
 | 
			
		||||
            "items": Object {
 | 
			
		||||
              "type": "string",
 | 
			
		||||
            },
 | 
			
		||||
            "type": "array",
 | 
			
		||||
          },
 | 
			
		||||
          "tag": Object {
 | 
			
		||||
            "items": Object {
 | 
			
		||||
              "items": Object {
 | 
			
		||||
                "type": "string",
 | 
			
		||||
              },
 | 
			
		||||
              "type": "array",
 | 
			
		||||
            },
 | 
			
		||||
            "type": "array",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "required": Array [],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "clientFeaturesSchema": Object {
 | 
			
		||||
        "properties": Object {
 | 
			
		||||
          "features": Object {
 | 
			
		||||
            "items": Object {
 | 
			
		||||
              "$ref": "#/components/schemas/clientFeatureSchema",
 | 
			
		||||
            },
 | 
			
		||||
            "type": "array",
 | 
			
		||||
          },
 | 
			
		||||
          "query": Object {
 | 
			
		||||
            "$ref": "#/components/schemas/clientFeaturesQuerySchema",
 | 
			
		||||
          },
 | 
			
		||||
          "segments": Object {
 | 
			
		||||
            "items": Object {
 | 
			
		||||
              "$ref": "#/components/schemas/segmentSchema",
 | 
			
		||||
            },
 | 
			
		||||
            "type": "array",
 | 
			
		||||
          },
 | 
			
		||||
          "version": Object {
 | 
			
		||||
            "type": "number",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "required": Array [
 | 
			
		||||
          "version",
 | 
			
		||||
          "features",
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "clientVariantSchema": Object {
 | 
			
		||||
        "additionalProperties": false,
 | 
			
		||||
        "properties": Object {
 | 
			
		||||
          "name": Object {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "payload": Object {
 | 
			
		||||
            "properties": Object {
 | 
			
		||||
              "type": Object {
 | 
			
		||||
                "type": "string",
 | 
			
		||||
              },
 | 
			
		||||
              "value": Object {
 | 
			
		||||
                "type": "string",
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "required": Array [
 | 
			
		||||
              "type",
 | 
			
		||||
              "value",
 | 
			
		||||
            ],
 | 
			
		||||
            "type": "object",
 | 
			
		||||
          },
 | 
			
		||||
          "weight": Object {
 | 
			
		||||
            "type": "number",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "required": Array [
 | 
			
		||||
          "name",
 | 
			
		||||
          "weight",
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "cloneFeatureSchema": Object {
 | 
			
		||||
        "properties": Object {
 | 
			
		||||
          "name": Object {
 | 
			
		||||
@ -952,7 +1097,6 @@ Object {
 | 
			
		||||
        },
 | 
			
		||||
        "required": Array [
 | 
			
		||||
          "name",
 | 
			
		||||
          "id",
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
@ -1537,14 +1681,18 @@ Object {
 | 
			
		||||
            "type": "array",
 | 
			
		||||
          },
 | 
			
		||||
          "description": Object {
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "id": Object {
 | 
			
		||||
            "type": "number",
 | 
			
		||||
          },
 | 
			
		||||
          "name": Object {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "required": Array [
 | 
			
		||||
          "name",
 | 
			
		||||
          "id",
 | 
			
		||||
          "constraints",
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
@ -5266,6 +5414,56 @@ If the provided project does not exist, the list of events will be empty.",
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "/api/client/features": Object {
 | 
			
		||||
      "get": Object {
 | 
			
		||||
        "operationId": "getAllClientFeatures",
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/clientFeaturesSchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "clientFeaturesSchema",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
          "client",
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "/api/client/features/{featureName}": Object {
 | 
			
		||||
      "get": Object {
 | 
			
		||||
        "operationId": "getClientFeature",
 | 
			
		||||
        "parameters": Array [
 | 
			
		||||
          Object {
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "name": "featureName",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": Object {
 | 
			
		||||
              "type": "string",
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/clientFeaturesSchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "clientFeaturesSchema",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
          "client",
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "/api/client/register": Object {
 | 
			
		||||
      "post": Object {
 | 
			
		||||
        "operationId": "registerClientApplication",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user