scim-patch
Advanced tools
Comparing version 0.8.0 to 0.8.1
@@ -1,1 +0,1 @@ | ||
{"processes":{"fe55b30f-70f4-4c0a-8f07-022a8e3930cd":{"parent":null,"children":[]}},"files":{"/home/runner/work/scim-patch/scim-patch/lib/src/scimPatch.js":["fe55b30f-70f4-4c0a-8f07-022a8e3930cd"],"/home/runner/work/scim-patch/scim-patch/lib/src/errors/scimErrors.js":["fe55b30f-70f4-4c0a-8f07-022a8e3930cd"]},"externalIds":{}} | ||
{"processes":{"c02fde37-31ac-4b78-b31c-28b4c5a1f539":{"parent":null,"children":[]}},"files":{"/home/runner/work/scim-patch/scim-patch/lib/src/scimPatch.js":["c02fde37-31ac-4b78-b31c-28b4c5a1f539"],"/home/runner/work/scim-patch/scim-patch/lib/src/errors/scimErrors.js":["c02fde37-31ac-4b78-b31c-28b4c5a1f539"]},"externalIds":{}} |
@@ -118,3 +118,3 @@ "use strict"; | ||
// We manipulate the object directly without knowing his property, that's why we use any. | ||
let resource = scimResource; | ||
let resources_scoped; | ||
validatePatchOperation(patch); | ||
@@ -124,3 +124,3 @@ // Path is supposed to be set, there are a validation in the validateOperation function. | ||
try { | ||
resource = navigate(resource, paths, { isRemoveOp: true }); | ||
resources_scoped = navigate(scimResource, paths, { isRemoveOp: true }); | ||
} | ||
@@ -137,18 +137,23 @@ catch (error) { | ||
// This is a mono valued property | ||
if (!patch.value) { | ||
// No value in the remove operation, we delete it. | ||
delete resource[lastSubPath]; | ||
return scimResource; | ||
for (const resource of resources_scoped) { | ||
if (!patch.value) { | ||
// No value in the remove operation, we delete it. | ||
delete resource[lastSubPath]; | ||
} | ||
else { | ||
// Value in the remove operation, we remove the children by value. | ||
resource[lastSubPath] = removeWithPatchValue(resource[lastSubPath], patch.value); | ||
} | ||
} | ||
// Value in the remove operation, we remove the children by value. | ||
resource[lastSubPath] = removeWithPatchValue(resource[lastSubPath], patch.value); | ||
return scimResource; | ||
} | ||
// The last element is an Array request. | ||
const { attrName, valuePath, array } = extractArray(lastSubPath, resource); | ||
// We keep only items who don't match the query if supplied. | ||
resource[attrName] = filterWithQuery(array, valuePath, { excludeIfMatchFilter: true }); | ||
// If the complex multi-valued attribute has no remaining records, the attribute SHALL be considered unassigned. | ||
if (resource[attrName].length === 0) | ||
delete resource[attrName]; | ||
for (const resource of resources_scoped) { | ||
// The last element is an Array request. | ||
const { attrName, valuePath, array } = extractArray(lastSubPath, resource); | ||
// We keep only items who don't match the query if supplied. | ||
resource[attrName] = filterWithQuery(array, valuePath, { excludeIfMatchFilter: true }); | ||
// If the complex multi-valued attribute has no remaining records, the attribute SHALL be considered unassigned. | ||
if (resource[attrName].length === 0) | ||
delete resource[attrName]; | ||
} | ||
return scimResource; | ||
@@ -159,3 +164,4 @@ } | ||
// We manipulate the object directly without knowing his property, that's why we use any. | ||
let resource = scimResource; | ||
// let resource: Record<string, any> = scimResource; | ||
let resources_scoped; | ||
validatePatchOperation(patch); | ||
@@ -168,7 +174,7 @@ if (!patch.path) | ||
try { | ||
resource = navigate(resource, paths); | ||
resources_scoped = navigate(scimResource, paths); | ||
} | ||
catch (e) { | ||
if (e instanceof scimErrors_1.FilterOnEmptyArray || e instanceof scimErrors_1.FilterArrayTargetNotFound) { | ||
resource = e.schema; | ||
const resource = e.schema; | ||
// check issue https://github.com/thomaspoignant/scim-patch/issues/42 to see why we should add this | ||
@@ -200,18 +206,25 @@ const parsedPath = (0, scim2_parse_filter_1.parse)(e.valuePath); | ||
if (!IS_ARRAY_SEARCH.test(lastSubPath)) { | ||
resource[lastSubPath] = addOrReplaceAttribute(resource[lastSubPath], patch); | ||
for (const resource of resources_scoped) { | ||
resource[lastSubPath] = addOrReplaceAttribute(resource[lastSubPath], patch); | ||
} | ||
return scimResource; | ||
} | ||
// The last element is an Array request. | ||
const { valuePath, array } = extractArray(lastSubPath, resource); | ||
// Get the list of items who are successful for the search query. | ||
const matchFilter = filterWithQuery(array, valuePath); | ||
// If the target location is a multi-valued attribute for which a value selection filter ("valuePath") has been | ||
// supplied and no record match was made, the service provider SHALL indicate failure by returning HTTP status | ||
// code 400 and a "scimType" error code of "noTarget". | ||
if (isReplaceOperation(patch.op) && matchFilter.length === 0) { | ||
throw new scimErrors_1.NoTarget(patch.path); | ||
for (const resource of resources_scoped) { | ||
const { valuePath, array } = extractArray(lastSubPath, resource); | ||
// Get the list of items who are successful for the search query. | ||
const matchFilter = filterWithQuery(array, valuePath); | ||
// If the target location is a multi-valued attribute for which a value selection filter ("valuePath") has been | ||
// supplied and no record match was made, the service provider SHALL indicate failure by returning HTTP status | ||
// code 400 and a "scimType" error code of "noTarget". | ||
if (isReplaceOperation(patch.op) && matchFilter.length === 0) { | ||
throw new scimErrors_1.NoTarget(patch.path); | ||
} | ||
for (let i = 0; i < array.length; i++) { | ||
// We are sure to find at least one index because matchFilter comes from array. | ||
if (matchFilter.includes(array[i])) { | ||
array[i] = addOrReplaceAttribute(array[i], patch); | ||
} | ||
} | ||
} | ||
// We are sure to find an index because matchFilter comes from array. | ||
const index = array.findIndex(item => matchFilter.includes(item)); | ||
array[index] = addOrReplaceAttribute(array[index], patch); | ||
return scimResource; | ||
@@ -244,3 +257,3 @@ } | ||
function navigate(inputSchema, paths, options = {}) { | ||
let schema = inputSchema; | ||
let schemas = [inputSchema]; | ||
for (let i = 0; i < paths.length - 1; i++) { | ||
@@ -250,28 +263,35 @@ const subPath = paths[i]; | ||
if (IS_ARRAY_SEARCH.test(subPath)) { | ||
try { | ||
const { attrName, valuePath, array } = extractArray(subPath, schema); | ||
// Get the item who is successful for the search query. | ||
const matchFilter = filterWithQuery(array, valuePath); | ||
// We are sure to find an index because matchFilter comes from array. | ||
const index = array.findIndex(item => matchFilter.includes(item)); | ||
if (index < 0) { | ||
throw new scimErrors_1.FilterArrayTargetNotFound('A matching array entry was not found using the supplied filter.', attrName, valuePath, schema); | ||
schemas = schemas.flatMap((schema) => { | ||
try { | ||
const { attrName, valuePath, array } = extractArray(subPath, schema); | ||
// Get the item who is successful for the search query. | ||
const matchFilter = filterWithQuery(array, valuePath); | ||
if (matchFilter.length === 0) { | ||
throw new scimErrors_1.FilterArrayTargetNotFound('A matching array entry was not found using the supplied filter.', attrName, valuePath, schema); | ||
} | ||
return matchFilter; | ||
} | ||
schema = array[index]; | ||
} | ||
catch (error) { | ||
if (error instanceof scimErrors_1.FilterOnEmptyArray) { | ||
error.schema = schema; | ||
catch (error) { | ||
/* | ||
FIXME In some edge cases, not fully compliant with SCIM, this can prematurely throw en error | ||
For example if we have a structure with multiValued complex | ||
attribute inside of a multiValued complex attribute, in | ||
this case this throw even if only a "branch" of the search fail. | ||
*/ | ||
if (error instanceof scimErrors_1.FilterOnEmptyArray) { | ||
error.schema = schema; | ||
} | ||
throw error; | ||
} | ||
throw error; | ||
} | ||
}); | ||
} | ||
else { | ||
// The element is not an array. | ||
if (!schema[subPath] && options.isRemoveOp) | ||
throw new scimErrors_1.InvalidRemoveOpPath(); | ||
schema = schema[subPath] || (schema[subPath] = {}); | ||
schemas = schemas.flatMap((schema) => { | ||
if (!schema[subPath] && options.isRemoveOp) | ||
throw new scimErrors_1.InvalidRemoveOpPath(); | ||
return schema[subPath] || (schema[subPath] = {}); | ||
}); | ||
} | ||
} | ||
return schema; | ||
return schemas; | ||
} | ||
@@ -296,2 +316,5 @@ /** | ||
} | ||
if (isReplaceOperation(patch.op)) { | ||
return property.map((it) => addOrReplaceAttribute(it, patch, multiValuedPathFilter)); | ||
} | ||
const a = property; | ||
@@ -298,0 +321,0 @@ if (!deepIncludes(a, patch.value)) |
@@ -316,2 +316,67 @@ "use strict"; | ||
}); | ||
it('REPLACE: replace on multiValued objects', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre" }, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const expected = "post"; | ||
const patch = { op: 'replace', value: expected, path: 'emails.newProperty' }; | ||
const afterPatch = (0, scimPatch_1.scimPatch)(scimUser, [patch]); | ||
(0, chai_1.expect)(afterPatch.emails[0].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
it('REPLACE: replace on multiValued objects without complete path', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre" }, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const expected = "post"; | ||
const patch = { op: 'replace', value: { newProperty: expected }, path: 'emails' }; | ||
const afterPatch = (0, scimPatch_1.scimPatch)(scimUser, [patch]); | ||
(0, chai_1.expect)(afterPatch.emails[0].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails.length).to.be.eq(4); | ||
return done(); | ||
}); | ||
it('REPLACE: filter that match multiple elements of complex multiValued attribute', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre" }, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const expected = "post"; | ||
const patch = { op: 'replace', value: expected, path: 'emails[primary eq false].newProperty' }; | ||
const afterPatch = (0, scimPatch_1.scimPatch)(scimUser, [patch]); | ||
(0, chai_1.expect)(afterPatch.emails[0].newProperty).to.be.eq("pre"); | ||
(0, chai_1.expect)(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
it('REPLACE: filter that match multiple elements of complex multiValued attribute without complete path', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre" }, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const expected = "post"; | ||
const patch = { op: 'replace', value: { newProperty: expected }, path: 'emails[primary eq false]' }; | ||
const afterPatch = (0, scimPatch_1.scimPatch)(scimUser, [patch]); | ||
(0, chai_1.expect)(afterPatch.emails[0].newProperty).to.be.eq("pre"); | ||
(0, chai_1.expect)(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
// see https://github.com/thomaspoignant/scim-patch/issues/489 | ||
@@ -767,2 +832,34 @@ it('REPLACE: Replace op with value of empty object results in circular reference', done => { | ||
}); | ||
it('ADD: filter that match multiple elements of complex multiValued attribute', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true }, | ||
{ value: "addr2@email.com", primary: false }, | ||
{ value: "addr3@email.com", primary: false }, | ||
{ value: "addr4@email.com", primary: false }, | ||
]; | ||
const expected = "post"; | ||
const patch = { op: 'add', value: expected, path: 'emails[primary eq false].newProperty' }; | ||
const afterPatch = (0, scimPatch_1.scimPatch)(scimUser, [patch]); | ||
(0, chai_1.expect)(afterPatch.emails[0].newProperty).to.be.an('undefined'); | ||
(0, chai_1.expect)(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
it('ADD: filter that match multiple elements of complex multiValued attribute without complete path', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true }, | ||
{ value: "addr2@email.com", primary: false }, | ||
{ value: "addr3@email.com", primary: false }, | ||
{ value: "addr4@email.com", primary: false }, | ||
]; | ||
const expected = "post"; | ||
const patch = { op: 'add', value: { newProperty: expected }, path: 'emails[primary eq false]' }; | ||
const afterPatch = (0, scimPatch_1.scimPatch)(scimUser, [patch]); | ||
(0, chai_1.expect)(afterPatch.emails[0].newProperty).to.be.an('undefined'); | ||
(0, chai_1.expect)(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
(0, chai_1.expect)(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
}); | ||
@@ -957,2 +1054,32 @@ describe('remove', () => { | ||
}); | ||
it('REMOVE: remove all sub-attributes in a complex multi-valued attribute', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre" }, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const patch = { op: 'remove', path: 'emails.newProperty' }; | ||
const afterPatch = (0, scimPatch_1.scimPatch)(scimUser, [patch]); | ||
(0, chai_1.expect)(afterPatch.emails[0].newProperty).to.be.an('undefined'); | ||
(0, chai_1.expect)(afterPatch.emails[1].newProperty).to.be.an('undefined'); | ||
(0, chai_1.expect)(afterPatch.emails[2].newProperty).to.be.an('undefined'); | ||
(0, chai_1.expect)(afterPatch.emails[3].newProperty).to.be.an('undefined'); | ||
return done(); | ||
}); | ||
it('REMOVE: filter that match multiple elements of complex multiValued attribute', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre" }, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const patch = { op: 'remove', path: 'emails[primary eq false].newProperty' }; | ||
const afterPatch = (0, scimPatch_1.scimPatch)(scimUser, [patch]); | ||
(0, chai_1.expect)(afterPatch.emails[0].newProperty).to.be.eq("pre"); | ||
(0, chai_1.expect)(afterPatch.emails[1].newProperty).to.be.an('undefined'); | ||
(0, chai_1.expect)(afterPatch.emails[2].newProperty).to.be.an('undefined'); | ||
(0, chai_1.expect)(afterPatch.emails[3].newProperty).to.be.an('undefined'); | ||
return done(); | ||
}); | ||
}); | ||
@@ -959,0 +1086,0 @@ describe('invalid requests', () => { |
{ | ||
"name": "scim-patch", | ||
"version": "0.8.0", | ||
"version": "0.8.1", | ||
"description": "SCIM Patch operation (rfc7644).", | ||
@@ -27,17 +27,17 @@ "main": "lib/src/scimPatch.js", | ||
"devDependencies": { | ||
"@types/benchmark": "2.1.4", | ||
"@types/chai": "4.3.9", | ||
"@types/mocha": "10.0.3", | ||
"@typescript-eslint/eslint-plugin": "6.8.0", | ||
"@typescript-eslint/parser": "6.8.0", | ||
"@types/benchmark": "2.1.5", | ||
"@types/chai": "4.3.11", | ||
"@types/mocha": "10.0.6", | ||
"@typescript-eslint/eslint-plugin": "6.13.1", | ||
"@typescript-eslint/parser": "6.13.1", | ||
"benchmark": "2.1.4", | ||
"chai": "4.3.10", | ||
"coveralls": "3.1.1", | ||
"eslint": "8.52.0", | ||
"eslint": "8.54.0", | ||
"eslint-plugin-mocha": "10.2.0", | ||
"eslint-plugin-testing-library": "6.1.0", | ||
"eslint-plugin-testing-library": "6.2.0", | ||
"mocha": "^10.2.0", | ||
"nyc": "15.1.0", | ||
"ts-node": "10.9.1", | ||
"typescript": "5.2.2" | ||
"typescript": "5.3.2" | ||
}, | ||
@@ -44,0 +44,0 @@ "dependencies": { |
@@ -171,3 +171,3 @@ import { | ||
// We manipulate the object directly without knowing his property, that's why we use any. | ||
let resource: Record<string, any> = scimResource; | ||
let resources_scoped: Record<string, any>[]; | ||
validatePatchOperation(patch); | ||
@@ -179,3 +179,3 @@ | ||
try { | ||
resource = navigate(resource, paths, {isRemoveOp: true}); | ||
resources_scoped = navigate(scimResource, paths, {isRemoveOp: true}); | ||
} catch (error) { | ||
@@ -193,22 +193,25 @@ if (error instanceof InvalidRemoveOpPath) { | ||
// This is a mono valued property | ||
if (!patch.value) { | ||
// No value in the remove operation, we delete it. | ||
delete resource[lastSubPath]; | ||
return scimResource; | ||
for (const resource of resources_scoped) { | ||
if (!patch.value) { | ||
// No value in the remove operation, we delete it. | ||
delete resource[lastSubPath]; | ||
} else { | ||
// Value in the remove operation, we remove the children by value. | ||
resource[lastSubPath] = removeWithPatchValue(resource[lastSubPath], patch.value); | ||
} | ||
} | ||
// Value in the remove operation, we remove the children by value. | ||
resource[lastSubPath] = removeWithPatchValue(resource[lastSubPath], patch.value); | ||
return scimResource; | ||
} | ||
for (const resource of resources_scoped) { | ||
// The last element is an Array request. | ||
const {attrName, valuePath, array} = extractArray(lastSubPath, resource); | ||
// The last element is an Array request. | ||
const {attrName, valuePath, array} = extractArray(lastSubPath, resource); | ||
// We keep only items who don't match the query if supplied. | ||
resource[attrName] = filterWithQuery<any>(array, valuePath, {excludeIfMatchFilter: true}); | ||
// We keep only items who don't match the query if supplied. | ||
resource[attrName] = filterWithQuery<any>(array, valuePath, {excludeIfMatchFilter: true}); | ||
// If the complex multi-valued attribute has no remaining records, the attribute SHALL be considered unassigned. | ||
if (resource[attrName].length === 0) | ||
delete resource[attrName]; | ||
// If the complex multi-valued attribute has no remaining records, the attribute SHALL be considered unassigned. | ||
if (resource[attrName].length === 0) | ||
delete resource[attrName]; | ||
} | ||
@@ -221,3 +224,4 @@ return scimResource; | ||
// We manipulate the object directly without knowing his property, that's why we use any. | ||
let resource: Record<string, any> = scimResource; | ||
// let resource: Record<string, any> = scimResource; | ||
let resources_scoped: Record<string, any>[]; | ||
validatePatchOperation(patch); | ||
@@ -233,6 +237,6 @@ | ||
try { | ||
resource = navigate(resource, paths); | ||
resources_scoped = navigate(scimResource, paths); | ||
} catch(e) { | ||
if (e instanceof FilterOnEmptyArray || e instanceof FilterArrayTargetNotFound) { | ||
resource = e.schema; | ||
const resource: Record<string, any> = e.schema; | ||
// check issue https://github.com/thomaspoignant/scim-patch/issues/42 to see why we should add this | ||
@@ -269,25 +273,33 @@ const parsedPath = parse(e.valuePath); | ||
} | ||
if (!IS_ARRAY_SEARCH.test(lastSubPath)) { | ||
resource[lastSubPath] = addOrReplaceAttribute(resource[lastSubPath], patch); | ||
return scimResource; | ||
for (const resource of resources_scoped) { | ||
resource[lastSubPath] = addOrReplaceAttribute(resource[lastSubPath], patch); | ||
} | ||
return scimResource; | ||
} | ||
// The last element is an Array request. | ||
const {valuePath, array} = extractArray(lastSubPath, resource); | ||
for (const resource of resources_scoped) { | ||
const {valuePath, array} = extractArray(lastSubPath, resource); | ||
// Get the list of items who are successful for the search query. | ||
const matchFilter = filterWithQuery<any>(array, valuePath); | ||
// Get the list of items who are successful for the search query. | ||
const matchFilter = filterWithQuery<any>(array, valuePath); | ||
// If the target location is a multi-valued attribute for which a value selection filter ("valuePath") has been | ||
// supplied and no record match was made, the service provider SHALL indicate failure by returning HTTP status | ||
// code 400 and a "scimType" error code of "noTarget". | ||
if (isReplaceOperation(patch.op) && matchFilter.length === 0) { | ||
throw new NoTarget(patch.path); | ||
// If the target location is a multi-valued attribute for which a value selection filter ("valuePath") has been | ||
// supplied and no record match was made, the service provider SHALL indicate failure by returning HTTP status | ||
// code 400 and a "scimType" error code of "noTarget". | ||
if (isReplaceOperation(patch.op) && matchFilter.length === 0) { | ||
throw new NoTarget(patch.path); | ||
} | ||
for (let i = 0; i < array.length; i++) { | ||
// We are sure to find at least one index because matchFilter comes from array. | ||
if(matchFilter.includes(array[i])){ | ||
array[i] = addOrReplaceAttribute(array[i], patch); | ||
} | ||
} | ||
} | ||
// We are sure to find an index because matchFilter comes from array. | ||
const index = array.findIndex(item => matchFilter.includes(item)); | ||
array[index] = addOrReplaceAttribute(array[index], patch); | ||
return scimResource; | ||
@@ -324,4 +336,4 @@ } | ||
*/ | ||
function navigate(inputSchema: any, paths: string[], options: NavigateOptions = {}): Record<string, unknown> { | ||
let schema = inputSchema; | ||
function navigate(inputSchema: any, paths: string[], options: NavigateOptions = {}): Record<string, unknown>[] { | ||
let schemas: any[] = [inputSchema]; | ||
for (let i = 0; i < paths.length - 1; i++) { | ||
@@ -331,28 +343,35 @@ const subPath = paths[i]; | ||
// We check if the element is an array with query (ex: emails[primary eq true). | ||
if (IS_ARRAY_SEARCH.test(subPath)) { | ||
try { | ||
const {attrName, valuePath, array} = extractArray(subPath, schema); | ||
// Get the item who is successful for the search query. | ||
const matchFilter = filterWithQuery<any>(array, valuePath); | ||
// We are sure to find an index because matchFilter comes from array. | ||
const index = array.findIndex(item => matchFilter.includes(item)); | ||
if (index < 0) { | ||
throw new FilterArrayTargetNotFound('A matching array entry was not found using the supplied filter.', attrName, valuePath, schema); | ||
if (IS_ARRAY_SEARCH.test(subPath)) { | ||
schemas = schemas.flatMap((schema)=>{ | ||
try { | ||
const {attrName, valuePath, array} = extractArray(subPath, schema); | ||
// Get the item who is successful for the search query. | ||
const matchFilter = filterWithQuery<any>(array, valuePath); | ||
if (matchFilter.length === 0) { | ||
throw new FilterArrayTargetNotFound('A matching array entry was not found using the supplied filter.', attrName, valuePath, schema); | ||
} | ||
return matchFilter; | ||
} catch (error) { | ||
/* | ||
FIXME In some edge cases, not fully compliant with SCIM, this can prematurely throw en error | ||
For example if we have a structure with multiValued complex | ||
attribute inside of a multiValued complex attribute, in | ||
this case this throw even if only a "branch" of the search fail. | ||
*/ | ||
if(error instanceof FilterOnEmptyArray){ | ||
error.schema = schema; | ||
} | ||
throw error; | ||
} | ||
schema = array[index]; | ||
} catch (error) { | ||
if(error instanceof FilterOnEmptyArray){ | ||
error.schema = schema; | ||
} | ||
throw error; | ||
} | ||
}); | ||
} else { | ||
// The element is not an array. | ||
if (!schema[subPath] && options.isRemoveOp) | ||
throw new InvalidRemoveOpPath(); | ||
schemas = schemas.flatMap((schema)=>{ | ||
if (!schema[subPath] && options.isRemoveOp) | ||
throw new InvalidRemoveOpPath(); | ||
schema = schema[subPath] || (schema[subPath] = {}); | ||
return schema[subPath] || (schema[subPath] = {}); | ||
}); | ||
} | ||
} | ||
return schema; | ||
return schemas; | ||
} | ||
@@ -379,2 +398,7 @@ | ||
if(isReplaceOperation(patch.op)){ | ||
return property.map( | ||
(it) => addOrReplaceAttribute(it,patch,multiValuedPathFilter) | ||
); | ||
} | ||
const a = property; | ||
@@ -381,0 +405,0 @@ if (!deepIncludes(a, patch.value)) |
@@ -350,2 +350,70 @@ import { | ||
it('REPLACE: replace on multiValued objects', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre"}, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const expected = "post"; | ||
const patch: ScimPatchAddReplaceOperation = {op: 'replace', value: expected, path: 'emails.newProperty'}; | ||
const afterPatch = scimPatch(scimUser, [patch]); | ||
expect(afterPatch.emails[0].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
it('REPLACE: replace on multiValued objects without complete path', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre"}, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const expected = "post"; | ||
const patch: ScimPatchAddReplaceOperation = {op: 'replace', value: {newProperty: expected}, path: 'emails'}; | ||
const afterPatch = scimPatch(scimUser, [patch]); | ||
expect(afterPatch.emails[0].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails.length).to.be.eq(4); | ||
return done(); | ||
}); | ||
it('REPLACE: filter that match multiple elements of complex multiValued attribute', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre"}, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const expected = "post"; | ||
const patch: ScimPatchAddReplaceOperation = {op: 'replace', value: expected, path: 'emails[primary eq false].newProperty'}; | ||
const afterPatch = scimPatch(scimUser, [patch]); | ||
expect(afterPatch.emails[0].newProperty).to.be.eq("pre"); | ||
expect(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
it('REPLACE: filter that match multiple elements of complex multiValued attribute without complete path', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre"}, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const expected = "post"; | ||
const patch: ScimPatchAddReplaceOperation = {op: 'replace', value: {newProperty: expected}, path: 'emails[primary eq false]'}; | ||
const afterPatch = scimPatch(scimUser, [patch]); | ||
expect(afterPatch.emails[0].newProperty).to.be.eq("pre"); | ||
expect(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
// see https://github.com/thomaspoignant/scim-patch/issues/489 | ||
@@ -836,2 +904,37 @@ it('REPLACE: Replace op with value of empty object results in circular reference', done => { | ||
}); | ||
it('ADD: filter that match multiple elements of complex multiValued attribute', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true }, | ||
{ value: "addr2@email.com", primary: false }, | ||
{ value: "addr3@email.com", primary: false }, | ||
{ value: "addr4@email.com", primary: false }, | ||
]; | ||
const expected = "post"; | ||
const patch: ScimPatchAddReplaceOperation = {op: 'add', value: expected, path: 'emails[primary eq false].newProperty'}; | ||
const afterPatch = scimPatch(scimUser, [patch]); | ||
expect(afterPatch.emails[0].newProperty).to.be.an('undefined'); | ||
expect(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
it('ADD: filter that match multiple elements of complex multiValued attribute without complete path', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true }, | ||
{ value: "addr2@email.com", primary: false }, | ||
{ value: "addr3@email.com", primary: false }, | ||
{ value: "addr4@email.com", primary: false }, | ||
]; | ||
const expected = "post"; | ||
const patch: ScimPatchAddReplaceOperation = {op: 'add', value: {newProperty: expected}, path: 'emails[primary eq false]'}; | ||
const afterPatch = scimPatch(scimUser, [patch]); | ||
expect(afterPatch.emails[0].newProperty).to.be.an('undefined'); | ||
expect(afterPatch.emails[1].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[2].newProperty).to.be.eq(expected); | ||
expect(afterPatch.emails[3].newProperty).to.be.eq(expected); | ||
return done(); | ||
}); | ||
}); | ||
@@ -1046,2 +1149,36 @@ describe('remove', () => { | ||
}); | ||
it('REMOVE: remove all sub-attributes in a complex multi-valued attribute', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre" }, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const patch: ScimPatchRemoveOperation = {op: 'remove', path: 'emails.newProperty'}; | ||
const afterPatch = scimPatch(scimUser, [patch]); | ||
expect(afterPatch.emails[0].newProperty).to.be.an('undefined'); | ||
expect(afterPatch.emails[1].newProperty).to.be.an('undefined'); | ||
expect(afterPatch.emails[2].newProperty).to.be.an('undefined'); | ||
expect(afterPatch.emails[3].newProperty).to.be.an('undefined'); | ||
return done(); | ||
}); | ||
it('REMOVE: filter that match multiple elements of complex multiValued attribute', done => { | ||
scimUser.emails = [ | ||
{ value: "addr1@email.com", primary: true, newProperty: "pre" }, | ||
{ value: "addr2@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr3@email.com", primary: false, newProperty: "pre" }, | ||
{ value: "addr4@email.com", primary: false, newProperty: "pre" }, | ||
]; | ||
const patch: ScimPatchRemoveOperation = {op: 'remove', path: 'emails[primary eq false].newProperty'}; | ||
const afterPatch = scimPatch(scimUser, [patch]); | ||
expect(afterPatch.emails[0].newProperty).to.be.eq("pre"); | ||
expect(afterPatch.emails[1].newProperty).to.be.an('undefined'); | ||
expect(afterPatch.emails[2].newProperty).to.be.an('undefined'); | ||
expect(afterPatch.emails[3].newProperty).to.be.an('undefined'); | ||
return done(); | ||
}); | ||
}); | ||
@@ -1048,0 +1185,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
369664
4024