Skip to content
Open
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
42 changes: 42 additions & 0 deletions src/adapter/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export class Adapter<

this.config.initialize()
this.normalizeEndpointNames()
this.filterEndpoints()
this.calculateRateLimitAllocations()
this.shutdownNotifier = new EventEmitter()
}
Expand Down Expand Up @@ -184,6 +185,47 @@ export class Adapter<
}
}

/**
* Filters the adapter's endpoints based on the ENABLED_ENDPOINTS setting.
* If ENABLED_ENDPOINTS is not set, all endpoints are loaded.
* If ENABLED_ENDPOINTS is set, only the specified endpoints are loaded.
* Throws an error if none of the specified endpoints match.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it should throw if any of the listed endpoints don't match. That would be a mistake that you want to find out sooner rather than later.

*/
private filterEndpoints() {
const enabledEndpoints = this.config.settings.ENABLED_ENDPOINTS
if (!enabledEndpoints) {
return
}

// Normalize the ENABLED_ENDPOINTS values
const enabledSet = new Set(enabledEndpoints.split(',').map((name) => name.trim().toLowerCase()))

const allNames = this.endpoints.map((e) => e.name)
this.endpoints = this.endpoints.filter((e) => enabledSet.has(e.name))

const excluded = allNames.filter((name) => !enabledSet.has(name))
if (excluded.length) {
logger.info(
`ENABLED_ENDPOINTS is set — loaded [${this.endpoints.map((e) => e.name).join(', ')}], excluded [${excluded.join(', ')}]`,
)
}

// Throws error if none of the specified endpoints match
if (this.endpoints.length === 0) {
throw new Error(
`ENABLED_ENDPOINTS is set to "${enabledEndpoints}" but none of the adapter endpoints match. Available: [${allNames.join(', ')}]`,
)
}

// Clear default endpoint if it is not included in the enabled endpoints
if (this.defaultEndpoint && !enabledSet.has(this.defaultEndpoint)) {
logger.warn(
`Default endpoint "${this.defaultEndpoint}" was excluded by ENABLED_ENDPOINTS, clearing it`,
)
this.defaultEndpoint = undefined
}
}

/**
* This function will take an adapter structure and go through each endpoint, calculating
* each one's allocation of the total rate limits that are set for the adapter as a whole.
Expand Down
6 changes: 6 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ export const BaseSettingsDefinition = {
description: 'Whether to use enableCompositeTransport parameter in AdapterEndpoint',
default: false,
},
ENABLED_ENDPOINTS: {
description:
'Comma-separated list of endpoint names to load. If not set, all endpoints are loaded. Useful for local development to reduce log noise.',
type: 'string',
sensitive: false,
},
} as const satisfies SettingsDefinitionMap

export const buildAdapterSettings = <
Expand Down
68 changes: 68 additions & 0 deletions test/adapter/filter-endpoints.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import test from 'ava'
import { Adapter, AdapterEndpoint } from '../../src/adapter'
import { LoggerFactoryProvider } from '../../src/util/logger'
import { NopTransport } from '../../src/util/testing-utils'

test.before(() => {
LoggerFactoryProvider.set()
})

const makeEndpoint = (name: string) => new AdapterEndpoint({ name, transport: new NopTransport() })

test.afterEach(() => {
delete process.env['ENABLED_ENDPOINTS']
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cleanup should go in beforeEach. If a test fails to clean up, you want that to fail, not some random next test.

This is more of an issue with jest where you can have before/afterEach apply to subsets of tests, but still a good rule of thumb to follow.

afterEach is only for assertions like "no errors happened during the test", or for cleanups that are not allowed to run initially.

})

test('loads all endpoints when ENABLED_ENDPOINTS is not set', (t) => {
const adapter = new Adapter({
name: 'TEST',
endpoints: [makeEndpoint('price'), makeEndpoint('volume')],
})
t.is(adapter.endpoints.length, 2)
})

test('filters to only the named endpoints', (t) => {
process.env['ENABLED_ENDPOINTS'] = 'price'
const adapter = new Adapter({
name: 'TEST',
endpoints: [makeEndpoint('price'), makeEndpoint('volume')],
})
t.is(adapter.endpoints.length, 1)
t.is(adapter.endpoints[0].name, 'price')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Combine these 2 lines into

t.deepEqual(adapter.endpoints.map(e => e.name), ['price'])

The error will be more useful than just the length not matching.

})

test('trims whitespace and lowercases names in ENABLED_ENDPOINTS', (t) => {
process.env['ENABLED_ENDPOINTS'] = ' Price , VOLUME '
const adapter = new Adapter({
name: 'TEST',
endpoints: [makeEndpoint('price'), makeEndpoint('volume')],
})
t.is(adapter.endpoints.length, 2)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same

})

test('throws when no endpoints match ENABLED_ENDPOINTS', (t) => {
process.env['ENABLED_ENDPOINTS'] = 'nonexistent'
t.throws(() => new Adapter({ name: 'TEST', endpoints: [makeEndpoint('price')] }), {
message: /nonexistent/,
})
})

test('clears defaultEndpoint when it is excluded by ENABLED_ENDPOINTS', (t) => {
process.env['ENABLED_ENDPOINTS'] = 'volume'
const adapter = new Adapter({
name: 'TEST',
defaultEndpoint: 'price',
endpoints: [makeEndpoint('price'), makeEndpoint('volume')],
})
t.is(adapter.defaultEndpoint, undefined)
})

test('keeps defaultEndpoint when it is included in ENABLED_ENDPOINTS', (t) => {
process.env['ENABLED_ENDPOINTS'] = 'price'
const adapter = new Adapter({
name: 'TEST',
defaultEndpoint: 'price',
endpoints: [makeEndpoint('price'), makeEndpoint('volume')],
})
t.is(adapter.defaultEndpoint, 'price')
})
Loading