Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mojaloop/#3984): parameterize switch id[BREAKING CHANGES] #385

Merged
merged 12 commits into from
Jun 25, 2024
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mojaloop/central-services-shared",
"version": "18.4.0",
"version": "18.5.0-snapshot.4",
"description": "Shared code for mojaloop central services",
"license": "Apache-2.0",
"author": "ModusBox",
Expand Down
6 changes: 1 addition & 5 deletions src/enums/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@

const Headers = {
FSPIOP: {
SWITCH: {
regex: /^switch$/i,
value: 'switch'
},
SOURCE: 'fspiop-source',
DESTINATION: 'fspiop-destination',
HTTP_METHOD: 'fspiop-http-method',
Expand Down Expand Up @@ -127,7 +123,7 @@ const RestMethods = {
const HeaderResources = {
PARTICIPANTS: 'participants',
ORACLE: 'oracle',
SWITCH: 'switch',
// SWITCH: 'switch', // @note: hub/switch name should now be passed in via service config.
TRANSFERS: 'transfers',
FX_TRANSFERS: 'fxTransfers',
QUOTES: 'quotes',
Expand Down
11 changes: 4 additions & 7 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ declare namespace CentralServicesShared {
interface HttpEnum {
Headers: {
FSPIOP: {
SWITCH: {
regex: RegExp;
value: string;
};
SOURCE: string;
DESTINATION: string;
HTTP_METHOD: string;
Expand Down Expand Up @@ -602,22 +598,23 @@ declare namespace CentralServicesShared {
interface Endpoints {
fetchEndpoints(fspId: string): Promise<any>
getEndpoint(switchUrl: string, fsp: string, endpointType: FspEndpointTypesEnum, options?: any): Promise<string>
initializeCache(policyOptions: object): Promise<boolean>
initializeCache(policyOptions: object, config: { hubName: string, hubNameRegex: RegExp }): Promise<boolean>
getEndpointAndRender(switchUrl: string, fsp: string, endpointType: FspEndpointTypesEnum, path: string, options?: any): Promise<string>
}

interface Participants {
getParticipant(switchUrl: string, fsp: string): Promise<object>
initializeCache(policyOptions: object): Promise<boolean>
initializeCache(policyOptions: object, config: { hubName: string, hubNameRegex: RegExp }): Promise<boolean>
}

interface ProtocolVersionsType {
content: string,
accept: string
}

type RequestParams = { url: string, headers: HapiUtil.Dictionary<string>, source: string, destination: string, hubNameRegex: RegExp, method?: RestMethodsEnum, payload?: any, responseType?: string, span?: any, jwsSigner?: any, protocolVersions?: ProtocolVersionsType }
interface Request {
sendRequest(url: string, headers: HapiUtil.Dictionary<string>, source: string, destination: string, method?: RestMethodsEnum, payload?: any, responseType?: string, span?: any, jwsSigner?: any, protocolVersions?: ProtocolVersionsType): Promise<any>
sendRequest(params: RequestParams): Promise<any>
}

interface Kafka {
Expand Down
26 changes: 21 additions & 5 deletions src/util/endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const Metrics = require('@mojaloop/central-services-metrics')
let client
let policy
let switchEndpoint
let hubName
let hubNameRegex

/**
* @function fetchEndpoints
Expand All @@ -58,10 +60,22 @@ const fetchEndpoints = async (fsp) => {
).startTimer()
try {
Logger.isDebugEnabled && Logger.debug(`[fsp=${fsp}] ~ participantEndpointCache::fetchEndpoints := Refreshing the cache for FSP: ${fsp}`)
const defaultHeaders = Http.SwitchDefaultHeaders(Enum.Http.HeaderResources.SWITCH, Enum.Http.HeaderResources.PARTICIPANTS, Enum.Http.HeaderResources.SWITCH)
if (!hubName) {
throw Error('"hubName" is not initialized. Initialize the cache first.')
}
if (!hubNameRegex) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't we just calculate hubNameRegex here based on hubName (to avoid making it required)?, e.g.:

const { HeaderValidation } = require('@mojaloop/central-services-shared').Util
...
const hubNameRegex = HeaderValidation.getHubNameRegex(Config.HUB_NAME)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. Would reduce the number of parameters there.

throw Error('"hubNameRegex" is not initialized. Initialize the cache first.')
}
const defaultHeaders = Http.SwitchDefaultHeaders(hubName, Enum.Http.HeaderResources.PARTICIPANTS, hubName)
const url = Mustache.render(switchEndpoint + Enum.EndPoints.FspEndpointTemplates.PARTICIPANT_ENDPOINTS_GET, { fsp })
Logger.isDebugEnabled && Logger.debug(`[fsp=${fsp}] ~ participantEndpointCache::fetchEndpoints := URL for FSP: ${url}`)
const response = await request.sendRequest(url, defaultHeaders, Enum.Http.HeaderResources.SWITCH, Enum.Http.HeaderResources.SWITCH)
const response = await request.sendRequest({
url,
headers: defaultHeaders,
source: hubName,
destination: hubName,
hubNameRegex
})
Logger.isDebugEnabled && Logger.debug(`[fsp=${fsp}] ~ Model::participantEndpoint::fetchEndpoints := successful with body: ${JSON.stringify(response.data)}`)
const endpoints = response.data
const endpointMap = {}
Expand All @@ -88,11 +102,11 @@ const fetchEndpoints = async (fsp) => {
* @function initializeCache
*
* @description This initializes the cache for endpoints
* @param {object} policyOptions The Endpoint_Cache_Config for the Cache being stored https://hapi.dev/module/catbox/api/?v=12.1.1#policy

* @param {object} policyOptions The Endpoint_Cache_Config for the Cache being stored https://hapi.dev/module/catbox/api/?v=12.1.1#policy
* @param {object} config The config object containing paramters used for the request function
* @returns {boolean} Returns true on successful initialization of the cache, throws error on failures
*/
exports.initializeCache = async (policyOptions) => {
exports.initializeCache = async (policyOptions, config) => {
try {
Logger.isDebugEnabled && Logger.debug(`participantEndpointCache::initializeCache::start::clientOptions - ${JSON.stringify(clientOptions)}`)
client = new Catbox.Client(CatboxMemory, clientOptions)
Expand All @@ -101,6 +115,8 @@ exports.initializeCache = async (policyOptions) => {
Logger.isDebugEnabled && Logger.debug(`participantEndpointCache::initializeCache::start::policyOptions - ${JSON.stringify(policyOptions)}`)
policy = new Catbox.Policy(policyOptions, client, partition)
Logger.isDebugEnabled && Logger.debug('participantEndpointCache::initializeCache::Cache initialized successfully')
hubName = config.hubName
hubNameRegex = config.hubNameRegex
return true
} catch (err) {
Logger.isErrorEnabled && Logger.error(`participantEndpointCache::Cache error:: ERROR:'${err}'`)
Expand Down
17 changes: 17 additions & 0 deletions src/util/headerValidation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,26 @@ const convertSupportedVersionToExtensionList = (supportedVersions) => {
return _.uniqWith(supportedVersionsExtensionListMap, _.isEqual)
}

let hubNameRegex

const escapeRegexInput = (str) => {
return str.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')
}

const getHubNameRegex = (hubName) => {
// @note: we do not expect hubName to change during runtime
// so we can cache the regex
if (!hubNameRegex) {
const regexStr = String.raw`^${escapeRegexInput(hubName)}$`
hubNameRegex = new RegExp(regexStr, 'i')
}
return hubNameRegex
}

module.exports = {
protocolVersions,
protocolVersionsMap,
getHubNameRegex,
generateAcceptRegex,
generateContentTypeRegex,
parseAcceptHeader,
Expand Down
15 changes: 10 additions & 5 deletions src/util/headers/transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const ErrorHandler = require('@mojaloop/central-services-error-handling')

const resourceVersions = require('../helpers').resourceVersions

const MISSING_FUNCTION_PARAMETERS = 'Missing parameters for function'

/**
* @module src/headers/transformer
*/
Expand Down Expand Up @@ -93,12 +95,15 @@ const getResourceInfoFromHeader = (headerValue) => {
* }
*
* @param {object} headers - the http header from the request
* @param {TransformHeadersConfig} headers - the http header from the request
* @param {TransformHeadersConfig} config - additional configuration for the transformation
*
* @returns {object} Returns the normalized headers
*/

const transformHeaders = (headers, config) => {
if (!config.hubNameRegex) {
throw ErrorHandler.Factory.createInternalServerFSPIOPError(MISSING_FUNCTION_PARAMETERS)
}
// Normalized keys
const normalizedKeys = Object.keys(headers).reduce(
function (keys, k) {
Expand Down Expand Up @@ -153,7 +158,7 @@ const transformHeaders = (headers, config) => {
case (ENUM.Headers.FSPIOP.HTTP_METHOD):
// Check to see if we find a regex match the source header containing the switch name.
// If so we include the signature otherwise we remove it.
if (headers[normalizedKeys[ENUM.Headers.FSPIOP.SOURCE]].match(ENUM.Headers.FSPIOP.SWITCH.regex) === null) {
if (headers[normalizedKeys[ENUM.Headers.FSPIOP.SOURCE]].match(config.hubNameRegex) === null) {
if (config.httpMethod.toLowerCase() === headerValue.toLowerCase()) {
// HTTP Methods match, and thus no change is required
normalizedHeaders[headerKey] = headerValue
Expand All @@ -178,7 +183,7 @@ const transformHeaders = (headers, config) => {
normalizedHeaders[headerKey] = config.destinationFsp
break
case (ENUM.Headers.GENERAL.ACCEPT.value):
if (!ENUM.Headers.FSPIOP.SWITCH.regex.test(config.sourceFsp)) {
if (!config.hubNameRegex.test(config.sourceFsp)) {
normalizedHeaders[headerKey] = headerValue
break
}
Expand All @@ -188,7 +193,7 @@ const transformHeaders = (headers, config) => {
normalizedHeaders[headerKey] = `application/vnd.interoperability.${resourceType}+json;version=${acceptVersion}`
break
case (ENUM.Headers.GENERAL.CONTENT_TYPE.value):
if (!ENUM.Headers.FSPIOP.SWITCH.regex.test(config.sourceFsp)) {
if (!config.hubNameRegex.test(config.sourceFsp)) {
normalizedHeaders[headerKey] = headerValue
break
}
Expand All @@ -202,7 +207,7 @@ const transformHeaders = (headers, config) => {
}
}

if (normalizedHeaders[normalizedKeys[ENUM.Headers.FSPIOP.SOURCE]].match(ENUM.Headers.FSPIOP.SWITCH.regex) !== null) {
if (normalizedHeaders[normalizedKeys[ENUM.Headers.FSPIOP.SOURCE]].match(config.hubNameRegex) !== null) {
// Check to see if we find a regex match the source header containing the switch name.
// If so we remove the signature added by default.
delete normalizedHeaders[normalizedKeys[ENUM.Headers.FSPIOP.SIGNATURE]]
Expand Down
7 changes: 6 additions & 1 deletion src/util/kafka/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,12 @@ const proceed = async (defaultKafkaConfig, params, opts) => {
}
if (fromSwitch) {
message.value.to = message.value.from
message.value.from = Enum.Http.Headers.FSPIOP.SWITCH.value

if (!opts.hubName) {
throw ErrorHandler.Factory.createInternalServerFSPIOPError('No hubName found in opts')
}

message.value.from = opts.hubName
if (message.value.content.headers) {
message.value.content.headers[Enum.Http.Headers.FSPIOP.SOURCE] = message.value.from
message.value.content.headers[Enum.Http.Headers.FSPIOP.DESTINATION] = message.value.to
Expand Down
28 changes: 23 additions & 5 deletions src/util/participants.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,14 @@ const Metrics = require('@mojaloop/central-services-metrics')
let client
let policy
let switchEndpoint
let hubName
let hubNameRegex

/**
* @function fetchParticipant
*
* @param {string} fsp The fsp id
* @param {object} options The options for the request function
* @description This populates the cache of participants
*
* @returns {object} participant Returns the object containing the participants
Expand All @@ -55,10 +59,22 @@ const fetchParticipant = async (fsp) => {
).startTimer()
try {
Logger.isDebugEnabled && Logger.debug('participantCache::fetchParticipant := Refreshing participant cache')
const defaultHeaders = Http.SwitchDefaultHeaders(Enum.Http.HeaderResources.SWITCH, Enum.Http.HeaderResources.PARTICIPANTS, Enum.Http.HeaderResources.SWITCH)
if (!hubName) {
throw Error('"hubName" is not initialized. Initialize the cache first.')
}
if (!hubNameRegex) {
throw Error('"hubNameRegex" is not initialized. Initialize the cache first.')
}
const defaultHeaders = Http.SwitchDefaultHeaders(hubName, Enum.Http.HeaderResources.PARTICIPANTS, hubName)
const url = Mustache.render(switchEndpoint + Enum.EndPoints.FspEndpointTemplates.PARTICIPANTS_GET, { fsp })
Logger.isDebugEnabled && Logger.debug(`participantCache::fetchParticipant := URL: ${url}`)
const response = await request.sendRequest(url, defaultHeaders, Enum.Http.HeaderResources.SWITCH, Enum.Http.HeaderResources.SWITCH)
const response = await request.sendRequest({
url,
headers: defaultHeaders,
source: hubName,
destination: hubName,
hubNameRegex
})
const participant = response.data
histTimer({ success: true })
return participant
Expand All @@ -72,11 +88,11 @@ const fetchParticipant = async (fsp) => {
* @function initializeCache
*
* @description This initializes the cache for endpoints
* @param {object} policyOptions The Endpoint_Cache_Config for the Cache being stored https://hapi.dev/module/catbox/api/?v=12.1.1#policy

* @param {object} policyOptions The Endpoint_Cache_Config for the Cache being stored https://hapi.dev/module/catbox/api/?v=12.1.1#policy
* @param {object} config The config object containing paramters used for the request function
* @returns {boolean} Returns true on successful initialization of the cache, throws error on failures
*/
exports.initializeCache = async (policyOptions) => {
exports.initializeCache = async (policyOptions, config) => {
try {
Logger.isDebugEnabled && Logger.debug(`participantCache::initializeCache::start::clientOptions - ${JSON.stringify(clientOptions)}`)
client = new Catbox.Client(CatboxMemory, clientOptions)
Expand All @@ -85,6 +101,8 @@ exports.initializeCache = async (policyOptions) => {
Logger.isDebugEnabled && Logger.debug(`participantCache::initializeCache::start::policyOptions - ${JSON.stringify(policyOptions)}`)
policy = new Catbox.Policy(policyOptions, client, partition)
Logger.isDebugEnabled && Logger.debug('participantCache::initializeCache::Cache initialized successfully')
hubName = config.hubName
hubNameRegex = config.hubNameRegex
return true
} catch (err) {
Logger.isErrorEnabled && Logger.error(`participantCache::Cache error:: ERROR:'${err}'`)
Expand Down
12 changes: 7 additions & 5 deletions src/util/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ request.defaults.httpAgent.toJSON = () => ({})
*@return {Promise<any>} The response for the request being sent or error object with response included
*/

const sendRequest = async (
const sendRequest = async ({
url,
headers,
source,
Expand All @@ -79,8 +79,9 @@ const sendRequest = async (
span = undefined,
jwsSigner = undefined,
protocolVersions = undefined,
axiosRequestOptionsOverride = {}
) => {
axiosRequestOptionsOverride = {},
hubNameRegex
}) => {
const histTimerEnd = Metrics.getHistogram(
'sendRequest',
`sending ${method} request to: ${url} from: ${source} to: ${destination}`,
Expand All @@ -92,15 +93,16 @@ const sendRequest = async (
sendRequestSpan.setTags({ source, destination, method, url })
}
let requestOptions
if (!url || !method || !headers || (method !== enums.Http.RestMethods.GET && method !== enums.Http.RestMethods.DELETE && !payload) || !source || !destination) {
if (!url || !method || !headers || (method !== enums.Http.RestMethods.GET && method !== enums.Http.RestMethods.DELETE && !payload) || !source || !destination || !hubNameRegex) {
throw ErrorHandler.Factory.createInternalServerFSPIOPError(MISSING_FUNCTION_PARAMETERS)
}
try {
const transformedHeaders = Headers.transformHeaders(headers, {
httpMethod: method,
sourceFsp: source,
destinationFsp: destination,
protocolVersions
protocolVersions,
hubNameRegex
})
requestOptions = {
url,
Expand Down
11 changes: 7 additions & 4 deletions test/unit/headers/transformer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const Helper = require('../../util/helper')
const headerConfigExample = {
httpMethod: 'PUT',
sourceFsp: 'switch',
destinationFsp: 'FSPDest'
destinationFsp: 'FSPDest',
hubNameRegex: /^Hub$/i
}

const headerDataInputExample = {
Expand Down Expand Up @@ -110,7 +111,8 @@ Test('Transfer Transformer tests', TransformerTest => {
const headerConfig = {
httpMethod: 'PUT',
sourceFsp: 'switch',
destinationFsp: 'FSPDest'
destinationFsp: 'FSPDest',
hubNameRegex: /^Hub$/i
}

const headerData = Util.clone(headerDataInputExample)
Expand All @@ -131,12 +133,13 @@ Test('Transfer Transformer tests', TransformerTest => {

const headerConfig = {
httpMethod: 'PUT',
sourceFsp: 'switch',
sourceFsp: 'Hub',
destinationFsp: 'FSPDest',
protocolVersions: {
content: '1.1',
accept: '1'
}
},
hubNameRegex: /^Hub$/i
}

const headerData = Util.clone(headerDataInputExample)
Expand Down
Loading
Loading