Describe the bug
I'm trying to write a new spec for a Backstage plugin that uses discriminated union types rendered as oneOf. I'm getting an AJV validationg error where Optic is using AJV to validate its own patched schema, which I think means that the patched schema is invalid. All of the code that I outline below can be found in this PR, here. Basically, I'm trying to use oneOf and enums to differentiate between two separate types. From what I can tell with json-schema-to-ts, my spec is correct, but I'm getting a few weird errors from AJV surfaced through Optic,
- enum contains multiple values that are the same
- value that shouldn't be an array is not an array
- value doesn't match schema from
anyOf
Full error messages below.
The types in question are,
type ConditionalPolicyDecision = {
result: AuthorizeResult.CONDITIONAL;
pluginId: string;
resourceType: string;
conditions: PermissionCriteria<PermissionCondition>;
};
type DefinitivePolicyDecision = {
result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
}
export type PolicyDecision =
| DefinitivePolicyDecision
| ConditionalPolicyDecision;
So far, my JSON schema (YAML) looks like this,
DefinitivePolicyDecision:
type: object
properties:
result:
type: string
enum:
- ALLOW
- DENY
id:
type: string
required:
- result
- id
additionalProperties: false
ConditionalPolicyDecision:
type: object
properties:
result:
type: string
enum:
- CONDITIONAL
pluginId:
type: string
resourceType:
type: string
conditions:
$ref: '#/components/schemas/PermissionCriteria'
id:
type: string
required:
- result
- id
- pluginId
- resourceType
- conditions
additionalProperties: true
PolicyDecision:
oneOf:
- $ref: '#/components/schemas/ConditionalPolicyDecision'
- $ref: '#/components/schemas/DefinitivePolicyDecision'
This results in the following schema (added a console.dir here),
|
const schema: SchemaObject = JSON.parse(JSON.stringify(input)); |
{
type: 'object',
properties: {
items: {
type: 'array',
items: {
oneOf: [
{
type: 'object',
properties: {
result: {
type: 'string',
enum: [ 'CONDITIONAL', 'ALLOW', 'ALLOW', 'DENY', 'DENY' ]
},
pluginId: { type: 'string' },
resourceType: { type: 'string' },
conditions: {
oneOf: [
{
type: 'object',
properties: {
allOf: {
type: 'array',
items: {
type: 'object',
properties: {
resourceType: { type: 'string' },
rule: { type: 'string' },
params: {
type: 'object',
additionalProperties: {
oneOf: [
{
oneOf: [
{ type: 'string' },
{ type: 'number' },
{
type: 'boolean',
nullable: true
}
]
},
{
type: 'array',
items: {
oneOf: [
{ type: 'string' },
{ type: 'number' },
{
type: 'boolean',
nullable: true
}
]
}
}
]
}
}
},
required: [ 'resourceType', 'rule' ],
additionalProperties: true
},
minItems: 1
}
},
additionalProperties: true
},
{
type: 'object',
properties: {
anyOf: {
type: 'array',
items: {
type: 'object',
properties: {
resourceType: { type: 'string' },
rule: { type: 'string' },
params: {
type: 'object',
additionalProperties: {
oneOf: [
{
oneOf: [
{ type: 'string' },
{ type: 'number' },
{
type: 'boolean',
nullable: true
}
]
},
{
type: 'array',
items: {
oneOf: [
{ type: 'string' },
{ type: 'number' },
{
type: 'boolean',
nullable: true
}
]
}
}
]
}
}
},
required: [ 'resourceType', 'rule' ],
additionalProperties: true
},
minItems: 1
}
},
additionalProperties: true
},
{
type: 'object',
properties: {
not: {
type: 'array',
items: {
type: 'object',
properties: {
resourceType: { type: 'string' },
rule: { type: 'string' },
params: {
type: 'object',
additionalProperties: {
oneOf: [
{
oneOf: [
{ type: 'string' },
{ type: 'number' },
{
type: 'boolean',
nullable: true
}
]
},
{
type: 'array',
items: {
oneOf: [
{ type: 'string' },
{ type: 'number' },
{
type: 'boolean',
nullable: true
}
]
}
}
]
}
}
},
required: [ 'resourceType', 'rule' ],
additionalProperties: true
},
minItems: 1
}
},
additionalProperties: true
},
{
type: 'object',
properties: {
resourceType: { type: 'string' },
rule: { type: 'string' },
params: {
type: 'object',
additionalProperties: {
oneOf: [
{
oneOf: [
{ type: 'string' },
{ type: 'number' },
{ type: 'boolean', nullable: true }
]
},
{
type: 'array',
items: {
oneOf: [
{ type: 'string' },
{ type: 'number' },
{ type: 'boolean', nullable: true }
]
}
}
]
}
}
},
required: [ 'resourceType', 'rule' ],
additionalProperties: true
}
]
},
id: { type: 'string' }
},
required: [ 'result', 'id' ],
additionalProperties: true
},
{
type: 'object',
properties: {
result: {
type: 'string',
enum: [ 'ALLOW', 'DENY', 'CONDITIONAL' ]
},
id: { type: 'string' },
pluginId: { type: 'string' },
resourceType: { type: 'string' },
conditions: {
type: 'object',
properties: {
rule: { type: 'string' },
params: { type: 'array', items: { type: 'string' } }
},
required: [ 'rule', 'params' ]
}
},
required: [ 'result', 'id' ],
additionalProperties: false
}
]
}
}
},
required: [ 'items' ],
additionalProperties: false
}
which throws an error during AJV compilation,
Error: schema is invalid: data/properties/items/items/oneOf/0/properties/result/enum must NOT have duplicate items (items ## 3 and 4 are identical), data/properties/items/items must be array, data/properties/items/items must match a schema in anyOf
at Ajv.validateSchema (/Users/sennyeya/.asdf/installs/nodejs/19.0.1/lib/node_modules/@useoptic/optic/node_modules/ajv/dist/core.js:266:23)
at Ajv._addSchema (/Users/sennyeya/.asdf/installs/nodejs/19.0.1/lib/node_modules/@useoptic/optic/node_modules/ajv/dist/core.js:460:18)
at Ajv.compile (/Users/sennyeya/.asdf/installs/nodejs/19.0.1/lib/node_modules/@useoptic/optic/node_modules/ajv/dist/core.js:158:26)
at ShapeDiffTraverser.traverse
...
Taking a look at the logs, this request causes part of the enum error by adding the "ALLOW" and "DENY" values twice. The other 2 errors I'm not sure about.
{
request: {
host: 'localhost:8001',
method: 'post',
path: '/authorize',
body: {
contentType: 'application/json',
body: '{"items":[{"id":"123","permission":{"type":"resource","name":"test.permission.1","resourceType":"test-resource-1","attributes":{}},"resourceRef":"resource:1"},{"id":"234","permission":{"type":"resource","name":"test.permission.2","resourceType":"test-resource-2","attributes":{}},"resourceRef":"resource:2"},{"id":"345","permission":{"type":"resource","name":"test.permission.3","resourceType":"test-resource-1","attributes":{}},"resourceRef":"resource:3"},{"id":"456","permission":{"type":"resource","name":"test.permission.4","resourceType":"test-resource-2","attributes":{}},"resourceRef":"resource:4"}]}',
size: 607
},
headers: [],
query: []
},
response: {
statusCode: '200',
body: {
contentType: 'application/json; charset=utf-8',
body: '{"items":[{"id":"123","result":"ALLOW"},{"id":"234","result":"ALLOW"},{"id":"345","result":"DENY"},{"id":"456","result":"DENY"}]}',
size: 129
},
headers: []
}
}
Internal optic schema diff after running the above request,
--- old.json 2024-01-11 16:39:58
+++ new.json 2024-01-11 16:01:40
@@ -8,7 +8,10 @@
{
"type": "object",
"properties": {
- "result": { "type": "string", "enum": ["CONDITIONAL"] },
+ "result": {
+ "type": "string",
+ "enum": ["CONDITIONAL", "ALLOW", "ALLOW", "DENY", "DENY"]
+ },
"pluginId": { "type": "string" },
"resourceType": { "type": "string" },
"conditions": {
@@ -179,13 +182,7 @@
},
"id": { "type": "string" }
},
- "required": [
- "result",
- "id",
- "pluginId",
- "resourceType",
- "conditions"
- ],
+ "required": ["result", "id"],
"additionalProperties": true
},
{
@@ -207,13 +204,7 @@
"required": ["rule", "params"]
}
},
- "required": [
- "result",
- "id",
- "pluginId",
- "resourceType",
- "conditions"
- ],
+ "required": ["result", "id"],
"additionalProperties": false
}
]
To Reproduce
See my PR here. I'm running PORT=8001 optic capture src/schema/openapi.yaml --server-override http://localhost:8001 in plugins/permission-backend to get the output seen above.
Expected behavior
An error is not thrown and the schema validates as expected.
Details (please complete the following information):
- OS and version: [e.g. Mac OS 13.1] MacOS 13.0.1
- Optic version: [e.g. v0.37.1] 0.52 and 0.53.20
- NodeJS version: [e.g. 18.0.0] 19.0.1
Describe the bug
I'm trying to write a new spec for a Backstage plugin that uses discriminated union types rendered as
oneOf. I'm getting an AJV validationg error where Optic is using AJV to validate its own patched schema, which I think means that the patched schema is invalid. All of the code that I outline below can be found in this PR, here. Basically, I'm trying to useoneOfand enums to differentiate between two separate types. From what I can tell withjson-schema-to-ts, my spec is correct, but I'm getting a few weird errors from AJV surfaced through Optic,anyOfFull error messages below.
The types in question are,
So far, my JSON schema (YAML) looks like this,
This results in the following schema (added a
console.dirhere),optic/projects/optic/src/commands/capture/patches/patchers/shapes/diff.ts
Line 301 in 8001d02
which throws an error during AJV compilation,
Taking a look at the logs, this request causes part of the enum error by adding the "ALLOW" and "DENY" values twice. The other 2 errors I'm not sure about.
Internal optic schema diff after running the above request,
To Reproduce
See my PR here. I'm running
PORT=8001 optic capture src/schema/openapi.yaml --server-override http://localhost:8001inplugins/permission-backendto get the output seen above.Expected behavior
An error is not thrown and the schema validates as expected.
Details (please complete the following information):