Skip to content

Introduce neo4j.hasReachableServer API #956

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

Merged
merged 6 commits into from
Jul 4, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ export default class DirectConnectionProvider extends PooledConnectionProvider {
)
}

getNegotiatedProtocolVersion () {
return new Promise((resolve, reject) => {
this._hasProtocolVersion(resolve)
.catch(reject)
})
}

async supportsTransactionConfig () {
return await this._hasProtocolVersion(
version => version >= BOLT_PROTOCOL_V3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
)
}

getNegotiatedProtocolVersion () {
return new Promise((resolve, reject) => {
this._hasProtocolVersion(resolve)
.catch(reject)
})
}

async verifyConnectivityAndGetServerInfo ({ database, accessMode }) {
const context = { database: database || DEFAULT_DB_NAME }

Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/connection-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ class ConnectionProvider {
throw Error('Not implemented')
}

/**
* Returns the protocol version negotiated via handshake.
*
* Note that this function call _always_ causes a round-trip to the server.
*
* @returns {Promise<number>} the protocol version negotiated via handshake.
* @throws {Error} When protocol negotiation fails
*/
getNegotiatedProtocolVersion (): Promise<number> {
throw Error('Not Implemented')
}

/**
* Closes this connection provider along with its internals (connections, pools, etc.)
*
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,19 @@ class Driver {
return connectionProvider.supportsUserImpersonation()
}

/**
* Returns the protocol version negotiated via handshake.
*
* Note that this function call _always_ causes a round-trip to the server.
*
* @returns {Promise<number>} the protocol version negotiated via handshake.
* @throws {Error} When protocol negotiation fails
*/
getNegotiatedProtocolVersion (): Promise<number> {
const connectionProvider = this._getOrCreateConnectionProvider()
return connectionProvider.getNegotiatedProtocolVersion()
}

/**
* Returns boolean to indicate if driver has been configured with encryption enabled.
*
Expand Down
22 changes: 22 additions & 0 deletions packages/neo4j-driver-lite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,26 @@ function driver (
}
}

/**
* Verifies if the driver can reach a server at the given url.
*
* @experimental
* @since 5.0.0
* @param {string} url The URL for the Neo4j database, for instance "neo4j://localhost" and/or "bolt://localhost"
* @param {Pick<Config, 'logging'>} config Configuration object. See the {@link driver}
* @returns {true} When the server is reachable
* @throws {Error} When the server is not reachable or the url is invalid
*/
async function hasReachableServer (url: string, config?: Pick<Config, 'logging'>): Promise<true> {
const nonLoggedDriver = driver(url, { scheme: 'none', principal: '', credentials: '' }, config)
try {
await nonLoggedDriver.getNegotiatedProtocolVersion()
return true
} finally {
await nonLoggedDriver.close()
}
}

const USER_AGENT: string = 'neo4j-javascript/' + VERSION

/**
Expand Down Expand Up @@ -393,6 +413,7 @@ const temporal = {
*/
const forExport = {
driver,
hasReachableServer,
int,
isInt,
isPoint,
Expand Down Expand Up @@ -444,6 +465,7 @@ const forExport = {

export {
driver,
hasReachableServer,
int,
isInt,
isPoint,
Expand Down
8 changes: 8 additions & 0 deletions packages/neo4j-driver-lite/test/integration/driver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,12 @@ describe('neo4j-driver-lite', () => {
expect(result.records[0].length).toEqual(1)
result.records[0].forEach(val => expect(val).toEqual(int(2)))
})

test('hasReachableServer success', async () => {
await expect(neo4j.hasReachableServer(`${scheme}://${hostname}`)).resolves.toBe(true)
})

test('hasReachableServer failure', async () => {
await expect(neo4j.hasReachableServer(`${scheme}://${hostname}:9999`)).rejects.toBeInstanceOf(Error)
})
})
3 changes: 2 additions & 1 deletion packages/neo4j-driver-lite/test/unit/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ describe('index', () => {
supportsMultiDb: async () => true,
supportsTransactionConfig: async () => true,
supportsUserImpersonation: async () => true,
verifyConnectivityAndGetServerInfo: async () => new ServerInfo({})
verifyConnectivityAndGetServerInfo: async () => new ServerInfo({}),
getNegotiatedProtocolVersion: async () => 5.0
}
})
expect(session).toBeDefined()
Expand Down
22 changes: 22 additions & 0 deletions packages/neo4j-driver/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,26 @@ function driver (url, authToken, config = {}) {
}
}

/**
* Verifies if the driver can reach a server at the given url.
*
* @experimental
* @since 5.0.0
* @param {string} url The URL for the Neo4j database, for instance "neo4j://localhost" and/or "bolt://localhost"
* @param {object} config Configuration object. See the {@link driver}
* @returns {true} When the server is reachable
* @throws {Error} When the server is not reachable or the url is invalid
*/
async function hasReachableServer (url, config) {
const nonLoggedDriver = driver(url, { scheme: 'none', principal: '', credentials: '' }, config)
try {
await nonLoggedDriver.getNegotiatedProtocolVersion()
return true
} finally {
await nonLoggedDriver.close()
}
}

const USER_AGENT = 'neo4j-javascript/' + VERSION

/**
Expand Down Expand Up @@ -385,6 +405,7 @@ const temporal = {
*/
const forExport = {
driver,
hasReachableServer,
int,
isInt,
isPoint,
Expand Down Expand Up @@ -437,6 +458,7 @@ const forExport = {

export {
driver,
hasReachableServer,
int,
isInt,
isPoint,
Expand Down
10 changes: 10 additions & 0 deletions packages/neo4j-driver/test/driver.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,16 @@ describe('#integration driver', () => {
})
})

it('hasReachableServer success', async () => {
await expectAsync(neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}`))
.toBeResolvedTo(true)
})

it('hasReachableServer failure', async () => {
await expectAsync(neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}:9999`))
.toBeRejected()
})

const integersWithNativeNumberEquivalent = [
[neo4j.int(0), 0],
[neo4j.int(42), 42],
Expand Down
7 changes: 7 additions & 0 deletions packages/neo4j-driver/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ declare function driver (
config?: Config
): Driver

declare function hasReachableServer (
url: string,
config?: Pick<Config, 'logging'>
): Promise<true>

declare const types: {
Node: typeof Node
Relationship: typeof Relationship
Expand Down Expand Up @@ -158,6 +163,7 @@ declare const temporal: {

declare const forExport: {
driver: typeof driver
hasReachableServer: typeof hasReachableServer
int: typeof int
isInt: typeof isInt
integer: typeof integer
Expand Down Expand Up @@ -218,6 +224,7 @@ declare const forExport: {

export {
driver,
hasReachableServer,
int,
isInt,
integer,
Expand Down