NAME

Neo4j::Driver::Plugin - Plug-in interface for Neo4j::Driver

VERSION

version 0.31

SYNPOSIS

 package Local::MyProxyPlugin;
 use parent 'Neo4j::Driver::Plugin';
 
 sub new { bless {}, shift }
 
 sub register {
   my ($self, $manager) = @_;
   $manager->add_event_handler( http_adapter_factory => sub {
     my ($continue, $driver) = @_;
     
     # Get and modify the default adapter
     # (currently Neo4j::Driver::Net::HTTP::LWP)
     my $adapter = $continue->();
     $adapter->ua->proxy('http', 'http://192.0.2.2:3128/');
     return $adapter;
   });
 }
 
 
 package main;
 use Neo4j::Driver 0.31;
 use Local::MyProxyPlugin;
 
 $driver = Neo4j::Driver->new();
 $driver->plugin('Local::MyProxyPlugin');

WARNING: EXPERIMENTAL

The design of the plug-in API is not finalised. You should probably let me know if you already are writing plug-ins, so that I can try to accommodate your use case and give you advance notice of changes.

The entire plug-in API is currently experimental.

The driver's plugin() method is experimental as well.

I'm grateful for any feedback you (yes, you!) might have on this driver's plug-in API. Please open a GitHub issue or get in touch via email (make sure you mention Neo4j in the subject to beat the spam filters).

DESCRIPTION

This is the abstract base class for Neo4j::Driver plug-ins. All plug-ins must inherit from Neo4j::Driver::Plugin (or perform the role another way). For a description of the required behaviour for plug-ins, see "METHODS" below.

Plug-ins can be used to extend and customise Neo4j::Driver to a significant degree. Upon being loaded, a plug-in will be asked to register event handlers with the driver. Handlers are references to custom subroutines defined by the plug-in. They will be invoked when the event they were registered for is triggered. Events triggered by the driver are specified in "EVENTS" below. Plug-ins can also define custom events.

The plug-in interface as described in this document is available since version 0.31.

EVENTS

This version of Neo4j::Driver can trigger the following events. Future versions may introduce new events or remove existing ones.

http_adapter_factory
 $manager->add_event_handler(
   http_adapter_factory => sub {
     my ($continue, $driver) = @_;
     my $adapter;
     ...
     return $adapter // $continue->();
   },
 );

This event is triggered when a new HTTP adapter instance is needed during session creation. Parameters given are a code reference for continuing with the next handler registered for this event and the driver.

A handler for this event must return the blessed instance of an HTTP adapter module (formerly known as "networking module") to be used instead of the default adapter built into the driver. See "Network adapter API for HTTP" below.

More events may be added in future versions. If you have a need for a specific event, let me know and I'll see if I can add it easily.

If your plug-in defines custom events of its own, it must only use event names that beginn with x_. All other event names are reserved for use by the driver itself.

METHODS

All plug-ins must implement the following methods, which are required for the Neo4j::Driver::Plugin role.

new
 sub new {
   my ($class) = @_;
   bless {}, $class;
 }

Plug-in constructor. Returns a blessed reference. Parameters given are the plug-in package name.

register
 sub register {
   my ($self, $manager) = @_;
   ...
 }

Called by the driver when a plug-in is loaded. Parameters given are the plug-in and a plug-in manager.

This method is expected to attach this plug-in's event handlers by calling the plug-in manager's "add_event_handler" method. See "EVENTS" for a list of events supported by this version of the driver.

THE PLUG-IN MANAGER

The job of the plug-in manager is to invoke the appropriate event handlers when events are triggered. It also allows clients to modify the list of registered handlers. A reference to the plug-in manager is provided to your plug-in when it is loaded; see "register".

The plug-in manager implements the following methods.

add_event_handler
 $manager->add_event_handler( event_name => sub {
   ...
 });

Registers the given handler for the named event. When that event is triggered, the handler will be invoked (unless another plug-in's handler for the same event prevents this). Handlers will be invoked the order they are added (but the order may be subject to change).

Certain events provide handlers with a code reference for continuing with the next handler registered for that event. This callback should be treated as the default driver action for that event. Depending on what a plug-in's purpose is, it may be useful to either invoke this callback and work with the results, or to ignore it entirely and handle the event independently.

 $manager->add_event_handler( get_value => sub {
   my ($continue) = @_;
   my $default = $continue->();
   return eval { maybe_value() } // $default;
 });

Note that future updates to the driver may change existing events to provide additional arguments. Because subroutine signatures perform strict checks of the number of arguments, they are not recommended for event handlers.

trigger_event
 $manager->trigger_event( 'event_name', @parameters );

Called by the driver to trigger an event and invoke any registered handlers for it. May be given an arbitrary number of parameters, all of which will be passed through to the event handler.

Most plug-ins won't need to call this method. But plug-ins may choose to trigger and handle custom events. These must have names that begin with x_. Plug-ins should not trigger events with other names, as these are reserved for internal use by the driver itself.

You should avoid using custom event names that start with x_after_ and x_before_, because a future version of the driver may give special treatment to such names. There is a chance that certain other names may similarly be affected.

Events that are triggered, but not handled, are currently silently ignored. This will likely change in a future version of the driver.

Calling this method in list context is discouraged, because doing so might be treated specially by a future version of the driver. Use scalar to be safe.

EXTENDING THE DRIVER

Module namespaces

Plug-in authors are free to use the Neo4j::Driver::Plugin:: namespace for modules that perform the Neo4j::Driver::Plugin role. Plug-ins in this namespace should be uploaded to CPAN.

Supporting modules should be placed in the sub-namespace defined by the plug-in. Network adapters may alternatively be placed in the Neo4j::Driver::Net:: namespaces, separated by network protocol. Adapters for generic modules should be given short names that indicate the module distribution (Neo4j::Driver::Net::HTTP::LWP for LWP::UserAgent etc.). The following module names are reserved for future use by the driver itself:

  • Neo4j::Driver::Net::Bolt::Base

  • Neo4j::Driver::Net::Bolt::Role

  • Neo4j::Driver::Net::HTTP::Base

  • Neo4j::Driver::Net::HTTP::Role

  • Neo4j::Driver::Net::HTTP::Tiny

Result handlers that perform the Neo4j::Driver::Result role should be placed in the Neo4j::Driver::Result:: namespace.

Please don't place plug-ins or other supporting modules directly into the Neo4j::Driver:: namespace.

Network adapter API for Bolt

At this time (2022), there is only one known implementation of the Bolt protocol for Perl: Neo4j::Bolt, created by Mark A. Jensen.

For this reason, an abstraction API for Bolt network adapters has not yet been specified. This driver currently expects that any module implementing Bolt behaves exactly like Neo4j::Bolt itself does.

If you're writing another Perl module for Bolt, please get in touch with me. I would like to collaborate with you on writing the abstraction API for the driver.

Network adapter API for HTTP

HTTP network adapters (formerly known as "HTTP networking modules") are used by the driver to delegate networking tasks to one of the common Perl modules for HTTP, such as LWP or Mojo::UserAgent. Driver plug-ins can also use this low-level access to implement special features, for example dynamic rewriting of Cypher queries or custom object-graph mapping.

The driver primarily uses HTTP network adapters by first calling the request() method, which initiates a request on the network, and then calling other methods to obtain information about the response. See Neo4j::Driver::Net for more information.

 $adapter->request('GET', '/', undef, 'application/json');
 $status  = $adapter->http_header->{status};
 $type    = $adapter->http_header->{content_type};
 $content = $adapter->fetch_all;

HTTP network adapters must implement the following methods.

date_header
 sub date_header {
   my ($self) = @_;
   ...
 }

Return the HTTP Date: header from the last response as string. If the server doesn't have a clock, the header will be missing; in this case, the value returned must be either the empty string or (optionally) the current time in non-obsolete RFC5322:3.3 format. May block until the response headers have been fully received.

fetch_all
 sub fetch_all {
   my ($self) = @_;
   ...
 }

Block until the response to the last network request has been fully received, then return the entire content of the response buffer.

This method must generally be idempotent, but the behaviour of this method if called after fetch_event() has already been called for the same request is undefined.

fetch_event
 sub fetch_event {
   my ($self) = @_;
   ...
 }

Return the next Jolt event from the response to the last network request as a string. When there are no further Jolt events, this method returns an undefined value. If the response hasn't been fully received at the time this method is called and the internal response buffer does not contain at least one event, this method will block until at least one event is available.

The behaviour of this method is undefined for responses that are not in Jolt format. The behaviour is also undefined if fetch_all() has already been called for the same request.

A future version of this driver will likely replace this method with something that performs JSON decoding on the event before returning it; this change may allow for better optimisation of Jolt event parsing.

http_header
 sub http_header {
   my ($self) = @_;
   ...
 }

Return a hashref with the following entries, representing headers and status of the last response.

  • content_typee. g. "application/json"

  • location – URI reference

  • status – status code, e. g. "404"

  • success – truthy for 2xx status codes

All of these entries must exist and be defined scalars. Unavailable values must use the empty string. Blocks until the response headers have been fully received.

http_reason
 sub http_reason {
   my ($self) = @_;
   ...
 }

Return the HTTP reason phrase (e. g. "Not Found" for status 404). If unavailable, "" is returned instead. May block until the response headers have been fully received.

json_coder
 sub json_coder {
   my ($self) = @_;
   ...
 }

Return a JSON::XS-compatible coder object (for result parsers). It must offer a method decode() that can handle the return values of fetch_event() and fetch_all() (which may be expected to be a byte sequence that is valid UTF-8) and should produce sensible output for booleans (e. g. $JSON::PP::true and $JSON::PP::false, or native booleans on newer Perls).

The default adapter included with the driver uses JSON::MaybeXS.

request
 sub request {
   my ($self, $method, $url, $json, $accept) = @_;
   ...
 }

Start an HTTP request on the network. The following positional parameters are given:

  • $method – HTTP method, e. g. "POST"

  • $url – string with request URL

  • $json – reference to hash of JSON object

  • $accept – string with value for the Accept: header

The request $url is to be interpreted relative to the server base URL given in the driver config.

The $json hashref must be serialised before transmission. It may include booleans encoded as the values \1 and \0. For requests to be made without request content, the value of $json will be undef.

$accept will have different values depending on $method; this is a workaround for a known issue in the Neo4j server (#12644).

The request() method may or may not block until the response has been received.

result_handlers
 sub result_handlers {
   my ($self) = @_;
   ...
 }

Return a list of result handler modules to be used to parse Neo4j statement results delivered through this module. The module names returned will be used in preference to the result handlers built into the driver.

Unlike any other method, result_handlers() is optional for a Neo4j HTTP adapter module. It may only be implemented by network adapters that actually offer custom result handlers. Note that the result handler API is currently internal and expected to change, and this method will likely disappear entirely in future; see "RESULT HANDLER API" below.

uri
 sub uri {
   my ($self) = @_;
   ...
 }

Return the server base URL as string or URI object (for Neo4j::Driver::ServerInfo). At least scheme, host, and port must be included.

Result handler API

Making a Neo4j result handler API available to plug-ins will require significant internal changes to the driver. These are currently being postponed, at least until most of the deprecated functionality has been removed from the driver's code base.

Even then, the result handler API may have low priority. However, new plug-in events are anticipated in a future version that should enable clients to achieve some of the same goals.

In the meantime, the result handler API remains not formally specified. It is an internal API that is evolving and may be subject to unannounced change; see "USE OF INTERNAL APIS".

A few notes on the result handler API that may or may not be accurate by the time you read this:

 sub new ($class, $params) {}
 sub _fetch_next ($self) {}
   # ^ optional if results are always fully detached
 sub _init_record ($self, $record) {}
 
 # for HTTP additionally:
 sub _accept_header ($, $want_jolt, $method) {}
 sub _acceptable ($, $content_type) {}
 sub _info ($self) {}
 sub _json ($self) {}
   # ^ only required for handlers that accept application/json
   #   (solely used by the Discovery API to get raw JSON)
 
 # For the API, these methods should gain public names (no _).
 # Currently the driver's own result handlers access the internal
 # data structures directly. For the API, some kind of accessors
 # will be needed, and for simplicity, all results should always
 # begin as attached (JSON: $fake_attached = 1).

WARNING: All of these methods are currently private APIs. See "USE OF INTERNAL APIS".

USE OF INTERNAL APIS

Public APIs generally include everything that is documented in POD, unless explicitly noted otherwise.

Private internals, on the other hand, include all package-global variables (our ...), all methods with names that begin with an underscore (_) and all cases of accessing the data structures of blessed objects directly (e. g. $session->{net}). In addition, methods without any POD documentation are to be considered private internals (e. g. Neo4j::Driver::Session->new()).

You are of course free to use any driver internals in your own code, but if you do so, you also bear the sole responsibility to keep it working after updates to the driver. Changes to internals are often not announced in the Changes list, so you should consider to watch GitHub commits. It is discouraged to try this approach if your code is used in production.

If you have difficulties achieving your goals without the use of driver internals or private APIs, you are most welcome to file a GitHub issue about that (or write to my CPAN email address with your concerns; make sure you mention Neo4j in the subject to beat the spam filters).

I can't promise that I'll be able to accommodate your use case, but I am going to try.

AUTHOR

Arne Johannessen <ajnn@cpan.org>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2016-2022 by Arne Johannessen.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)