Skip to content

Commit

Permalink
feat(dinero): add synchronous convert method to API
Browse files Browse the repository at this point in the history
Add an alternative, synchronous API to the async convert() that can be used synchronously when the
exchange rates are known at the moment of conversion.

re dinerojs#73
  • Loading branch information
vipera committed Mar 18, 2021
1 parent 88625e5 commit 57bf229
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 12 deletions.
75 changes: 68 additions & 7 deletions src/dinero.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ const Dinero = options => {
)
}

/**
* Uses ES5 function notation so `this` can be passed through call, apply and bind
* @ignore
*/
const createConvert = function(options, createOptions) {
return create.call(this, {
amount: calculator.round(
calculator.multiply(this.getAmount(), parseFloat(options.rate)),
createOptions.roundingMode
),
currency: options.currency
})
}

/**
* Uses ES5 function notation so `this` can be passed through call, apply and bind
* @ignore
Expand Down Expand Up @@ -533,15 +547,62 @@ const Dinero = options => {
`No rate was found for the destination currency "${currency}".`,
TypeError
)
return create.call(this, {
amount: calculator.round(
calculator.multiply(this.getAmount(), parseFloat(rate)),
options.roundingMode
),
currency
})
return createConvert.call(this, { rate, currency }, options)
})
},
/**
* Immediately returns a new Dinero object converted to another currency.
* This is a non-async version of convert, and is useful if you already have
* your own pre-loaded exchange rates from some source.
*
* @see Dinero.convert for the asynchronous version that can look up
* exchange rates.
*
* @param {String} currency - The destination currency, expressed as an {@link https://en.wikipedia.org/wiki/ISO_4217#Active_codes ISO 4217 currency code}.
* @param {Object} options.endpoint - The "endpoint" with exchange rates. Provide the exchanges rates as an object that would be received by the convert method.
* @param {String} [options.propertyPath='rates.{{to}}'] - The property path to the rate.
* @param {String} [options.roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
*
* @example
* // usage with exchange rates provided as a rates hash
* // using the default `propertyPath` format (so it doesn't have to be specified)
* const rates = {
* rates: {
* EUR: 0.81162
* }
* }
*
* Dinero({ amount: 500 })
* .convert('EUR', {
* endpoint: rates
* })
*/
convertSync(
currency,
{
endpoint = globalExchangeRatesApi.endpoint,
propertyPath = globalExchangeRatesApi.propertyPath || 'rates.{{to}}',
roundingMode = globalRoundingMode
} = {}
) {
assert(
typeof endpoint === 'object',
'Exchange rates endpoint should be an object with preloaded exchange rates',
TypeError
)
const options = Object.assign(
{},
{
endpoint,
propertyPath,
roundingMode
}
)
const rate = CurrencyConverter(options).getExchangeRateSync(
endpoint, this.getCurrency(), currency
)
return createConvert.call(this, { rate, currency }, options)
},
/**
* Checks whether the value represented by this object equals to the other.
*
Expand Down
21 changes: 16 additions & 5 deletions src/services/currency-converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export default function CurrencyConverter(options) {
headers: options.headers
})

const getRateFromObject = (data, options, from, to) =>
flattenObject(data)[mergeTags(options.propertyPath, { from, to })]

return {
/**
* Returns the exchange rate.
Expand All @@ -29,10 +32,18 @@ export default function CurrencyConverter(options) {
return (isThenable(options.endpoint)
? options.endpoint
: getRatesFromRestApi(from, to)
).then(
data =>
flattenObject(data)[mergeTags(options.propertyPath, { from, to })]
)
}
).then(data => getRateFromObject(data, options, from, to))
},
/**
* Returns the exchange rate when the exchange rates table can be provided
* immediately.
* @ignore
*
* @param {Object} data - Exchange rate data.
* @param {String} from - The base currency.
* @param {String} to - The destination currency.
*/
getExchangeRateSync: (data, from, to) =>
getRateFromObject(data, options, from, to),
}
}
24 changes: 24 additions & 0 deletions test/unit/dinero.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,30 @@ describe('Dinero', () => {
await expect(Dinero({ amount: 500 }).convert('EURO')).rejects.toThrow()
})
})
describe('#convertSync', () => {
let exchangeRates;
beforeEach(() => {
exchangeRates = {
USD: {
data: {
rates: {
EUR: 0.8,
USD: 1,
}
}
}
}
})
test('should return a new converted Dinero object when base and destination currencies are valid', async () => {
const res = await Dinero({ amount: 500, currency: 'USD' }).convertSync('EUR', {
endpoint: exchangeRates.USD,
})
expect(res.toObject()).toMatchObject({
amount: 400,
currency: 'EUR'
})
})
})
describe('#equalsTo()', () => {
test('should return true when both amount and currencies are equal', () => {
expect(
Expand Down

0 comments on commit 57bf229

Please sign in to comment.