Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:

strategy:
matrix:
node-version: [14.x]
node-version: [18.x]

steps:
- uses: actions/checkout@v2
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/coverage-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ jobs:
runs-on: ubuntu-latest
steps:

- uses: actions/checkout@v1
- uses: actions/checkout@v4

- name: Use Node.js 14.x
uses: actions/setup-node@v1
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x

- name: npm install, make test-coverage
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14
node-version: 18
- run: npm i
- run: npm test

Expand All @@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14
node-version: 18
registry-url: https://registry.npmjs.org/
- run: npm i
- run: npm run build-types
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,19 @@ Determines if the api response data should be logged or not.
Determines if the api response body should be logged or not.

- **excludeFieldsLogRequestData**. *string array*.
Returns the fields to exclude from the api request data.
Returns the fields to exclude from the api request data passing simple fields or specific paths to such fields.

- **excludeFieldsLogResponseBody**. *string array*.
Returns the fields to exclude from the api response body. The fields will be omitted recursively.
Returns the fields to exclude from the api response data passing simple fields or specific paths to such fields.

ℹ️ **Note**:
- The wildcard `*` in the field path of the `excludeFieldsLogRequestData` or `excludeFieldsLogResponseBody` static getter, is used to access properties inside arrays of one level or nested arrays.
- The wildcard `**` in the field path of the `excludeFieldsLogRequestData` or `excludeFieldsLogResponseBody` static getter, can be used when the intermediate field path is unknown between the root and the field to exclude.
- The `excludeFieldsLogRequestData` or `excludeFieldsLogResponseBody` static getter can have both field names and field paths.

⚠️ **Warning**:
- When using the wildcard `*` alone in the field path of the `excludeFieldsLogRequestData` or `excludeFieldsLogResponseBody` static getter, it will exclude all the fields in the log.
- In case the field path is incorrect, it will not exclude any field.

### Setters

Expand Down
4 changes: 2 additions & 2 deletions lib/helpers/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ module.exports = class LogHelper {
api: { endpoint, httpMethod },
request: {
...api.shouldLogRequestHeaders && { headers: omitRecursive(headers, ['janis-api-key', 'janis-api-secret']) },
...api.shouldLogRequestData && { data: omitRecursive(pristineData, api.excludeFieldsLogRequestData) },
...api.shouldLogRequestData && { data: omitRecursive(pristineData, api.excludeFieldsLogRequestData || []) },
...this.addServiceName(headers)
},
response: {
code: response.code,
headers: response.headers,
...api.shouldLogResponseBody && { body: omitRecursive(response.body, api.excludeFieldsLogResponseBody) }
...api.shouldLogResponseBody && { body: omitRecursive(response.body, api.excludeFieldsLogResponseBody || []) }
}
};

Expand Down
116 changes: 104 additions & 12 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict';

const omit = require('lodash.omit');

/**
* Determines if the passed value is an object.
*
Expand All @@ -12,36 +10,130 @@ const isObject = value => {
return !!value && typeof value === 'object' && !Array.isArray(value);
};

/**
* Checks if a given property path matches a specific pattern.
*
* @param {string[]} path - The current property path segments.
* @param {string[]} pattern - The pattern segments to match against.
* @param {number} i - Current index in the path array.
* @param {number} j - Current index in the pattern array.
* @returns {boolean} True if the path matches the pattern, false otherwise.
*/
const isPathMatch = (path, pattern, i = 0, j = 0) => {

// Special case: if pattern has only one segment, check if it matches the last path segment or is a wildcard
if(pattern.length === 1)
return path[path.length - 1] === pattern[0] || pattern[0] === '*';

// If all pattern segments are consumed, check if all path segments are also consumed
if(j === pattern.length)
return i === path.length;

// Handle double wildcard '**' which matches zero or more path segments
if(pattern[j] === '**') {

// Try matching the rest of the pattern starting from each possible position in the path
for(let k = i; k <= path.length; k++) {
// Recursively check if the remaining pattern matches from position k
if(isPathMatch(path, pattern, k, j + 1))
return true;
}

// If no match found with any position, return false
return false;
}

// If all path segments are consumed but still have pattern segments left, no match
if(i === path.length)
return false;

// If current pattern segment is not a wildcard and doesn't match current path segment, no match
if(pattern[j] !== '*' && pattern[j] !== path[i])
return false;

// Move to next segments in both path and pattern
return isPathMatch(path, pattern, i + 1, j + 1);
};

/**
* Recursively iterates over the object/array structure to find and remove matching properties.
*
* @param {string[]} patterns - The patterns as path segments of the properties to exclude when matching.
* @param {*} current - The current element being processed (object, array, or primitive).
* @param {string[]} currentPath - The path segments leading to the current element.
*/
const recurse = (patterns, current, currentPath = []) => {

// If current element is an array, recursively process each item with its index as path segment
if(Array.isArray(current)) {

// Iterate through all items in the array
for(const [index, item] of current.entries()) {
currentPath.push(String(index));
recurse(patterns, item, currentPath);
currentPath.pop();
}

} else if(current && typeof current === 'object') {

// Iterate through all object keys
for(const key of Object.keys(current)) {

// Add the current key to the path
currentPath.push(key);

// Check if the current path matches any of the exclusion patterns
if(patterns.some(p => isPathMatch(currentPath, p)))
// If it matches, delete this property from the object
delete current[key];
else
// If it doesn't match, recursively process the value
recurse(patterns, current[key], currentPath);

// Remove the key from the path after processing
currentPath.pop();
}
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no se si viene de copy paste de otro lado o es nuevo pero me parece bastante nefasto en performance dar soporte al ** ya que hicimos eso por lo menos a nivel código intentaría atajarlo

  1. forEach en esto es un rotundamente no... todos los MS van a pasar por acá eso es lento -> for ... of
  2. este ya es un poco mas fino pero estamos recreando e iterando los array y además recursivo, para esto se puede evitar con .push() y .pop(), de esa forma solo vamos actualizando por iteración sin importar la profundidad, con push antes de entrar y pop al salir para mantener el array
  3. el Object.entries() se recontra tiene que ir, es Object.keys(), de esa forma evitamos la doble iteración de entries para "accder lindo" con value a fin de cuenta lo único que cambia es el llamado a recurse del final


/**
* Returns a new object excluding one or more properties recursively
*
* @param {Object<string, *>} object
* @param {string|Array<string>} exclude
* @param {object} object - The input object to process that will be cloned and modified.
* @param {string[]} pathPatterns - Property path(s) to exclude (e.g. ['a.b', '*.x.y', 'some-field']).
* @returns {object} A new object with specified properties omitted.
*/
const omitRecursive = (object, exclude) => {
const omitRecursive = (object, pathPatterns) => {

// Convert dot-notation path patterns into arrays of path segments.
const patterns = [];

for(const p of pathPatterns)
patterns.push(p.split('.'));

object = { ...object }; // Avoid original object modification
// Deep clone the original object to avoid modifying it directly.
const clonedObject = structuredClone(object);

Object.entries(object).forEach(([key, value]) => {
object[key] = isObject(value) ? omitRecursive(value, exclude) : value;
});
// Recursive removal process from the root of the cloned object
recurse(patterns, clonedObject);

return omit(object, exclude);
// Return the modified clone with specified properties removed
return clonedObject;
};

const trimObjectValues = value => {

if(typeof value !== 'object' || !value)
return value;

Object.keys(value).forEach(k => {
for(const k of Object.keys(value)) {

if((typeof value[k] === 'string'))
value[k] = value[k].trim();

if(typeof value[k] === 'object' && value[k] !== null)
value[k] = trimObjectValues(value[k]);
});
}

return value;
};
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"@janiscommerce/api-session": "^3.4.0",
"@janiscommerce/log": "^5.0.6",
"@janiscommerce/superstruct": "^1.2.1",
"lodash.clonedeep": "^4.5.0",
"lodash.omit": "4.5.0"
"lodash.clonedeep": "^4.5.0"
}
}
Loading