=begin pod

=head1 NAME

JSON::Webhook - provide webhook logic for processing JSON payloads

=head1 SYNOPSIS

=begin code :lang<raku>
use JSON::Webhook;

JSON::Webhook.new :$host, :$port, :$application;
=end code

=head1 DESCRIPTION

The C<JSON::Webhook> distribution provides a C<JSON::Webhook> role that
provides the logic for running a webservice that accepts JSON payloads
(typically from services such as Github / Codeberg) and collects them
using the logic provided by
L<JSON::Collector|https://raku.land/zef:lizmat/JSON::Collector>.

Customization can either be done by providing extra named arguments
to the C<new> and/or C<application> methods, or by consuming the
C<JSON::Webhook> role into a class that provides customized
C<collector> and C<processor> methods.

=begin code :lang<raku>
use JSON::Webhook;

# Custom class that inserts the value of the "channels" named
# argument from the request into the data structure to be saved
class JSON::Webhook::Channels does JSON::Webhook {
    method processor() {
        -> \data, \request {
            my %data = data;
            %data<channels> := eager .split(",")
              with request.query-hash<channels>;
            %data
        }
    }
}
=end code

=head1 METHODS

=head2 new

=begin code :lang<raku>
say "starting server";
JSON::Webhook.new :$host, :$port, :$application, ...
say "server stopped";
=end code

The C<new> method provides the simplest way to set up an endpoint
for a JSON push service.  It creates a C<JSON::Webhook> object with
the given arguments.

If a C<JSON::Webhook> is sunk, it calls the C<serve> method which
starts listening on the indicated host IP and port and activates
a control-C handler to stop the server in an orderly manner.

It takes the following named arguments:

=head3 :host

The name of the endpoint to set up a listening server on.  Defaults to
calling the C<host> method, which by default returns the value of the
dynamic variable C<$*WEBHOOK-HOST>, the environment variable
C<WEBHOOK_HOST> or C<"localhost">.

=head3 :port

The port number on which the server will be listening on.  Defaults to
calling the C<port> method, which by default returns the value of the
dynamic variable C<$*WEBHOOK-PORT>, the environment variable
C<WEBHOOK_PORT> or C<9999>.

=head3 :application

The C<Cro::HTTP::Router::RouteSet> compatible object that will be used
to run the server with.  If not specified, will call the C<application>
class method with the rest of the named arguments to set up the
application.

=head3 additional named argments

Any additional named arguments are passed on as appropriate.

=head2 application

The C<application> instance method returns whatever was (implicitely)
specified with C<:application> named argument to the C<new> method.

=begin code :lang<raku>
my $application = JSON::Webhook.application :$collector, :$processor, ...
JSON::Webhook.new(:$host, :$port, :$application);
=end code

The C<application> class method returns a C<Cro::HTTP::Router::RouteSet>
compatible object that can be used as the "application" in setting up
a C<JSON::Webhook> object.

It takes the following named arguments:

=head3 :collector

The object with C<JSON::Collector> semantics that will be used to store
any incoming JSON payloads.  If not specified, it will call the
C<collector> method, which by default creates a C<JSON::Collector>
object with any additional named arguments passed.

=head3 :processor

A C<Callable> that will be called with the data structure representing
the JSON payload that was received, and the
L<C<Cro::HTTP::Request>|https://cro.raku.org/docs/reference/cro-http-request>
object.  It should return the data structure that should be stored by the
collector.  If not specified, it will call the C<processor> method, which
by default returns a C<Callable> that will just return the data structure
of the JSON payload verbatim.

=head2 serve

=begin code :lang<raku>
my $webhook = JSON::Webhook.new(:$host, :$port, :$application);
say "starting";
$webhook.serve;
say "stopped";
=end code

The C<serve> method takes an instantiated C<JSON::Webhook> object, starts
listening on the given C<host> and C<port>, and starts serving requests
with the given <application>.  Also installs a control-C handler for an
orderly shutdown when control-C is pressed.

=head2 stop

=begin code :lang<raku>
my $webhook = JSON::Webhook.new(:$host, :$port, :$application);
say "starting";
start {
    $webhook.serve;
    say "stopped";
}
...
$webhook.stop;  # stopped
=end code

The C<stop> method will stop the server from accepting requests, and
thus continue execution B<after> the call to C<serve>.

=head1 OVERRIDABLE METHODS

These methods can be overriden by a consumer of the C<JSON::Webhook>
class to customize behaviour.

=head2 host

Expected to return a value that can be used as a C<:host> argument to
C<Cro::HTTP::Server.new>.  By default returns the value of the
dynamic variable C<$*WEBHOOK-HOST>, the environment variable
C<WEBHOOK_HOST> or C<"localhost">.

=head2 port

Expected to return a value that can be used as a C<:port> argument to
C<Cro::HTTP::Server.new>.  By default returns the value of the
dynamic variable C<$*WEBHOOK-PORT>, the environment variable
C<WEBHOOK_PORT> or C<9999>.

=head2 collector

Expected to return an object with C<JSON::Collector> semantics.
By default creates a C<JSON::Collector> object with any additional
named arguments passed.

=head2 processor

Expected to return a C<Callable> that will take the data structure
parsed from a JSON payload, and the
L<C<Cro::HTTP::Request>|https://cro.raku.org/docs/reference/cro-http-request>
object, and return a possibly adapted data structure to be stored by
the collector.  By default, this is a simple C<Callable> that returns
the data structure unchanged.

=head1 AUTHOR

Elizabeth Mattijsen <liz@raku.rocks>

=head1 COPYRIGHT AND LICENSE

Copyright 2026 Elizabeth Mattijsen

Source can be located at: https://codeberg.org/lizmat/JSON-Collector .
Comments and Pull Requests are welcome.

If you like this module, or what I'm doing more generally, committing to a
L<small sponsorship|https://github.com/sponsors/lizmat/>  would mean a great
deal to me!

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.

=end pod

# vim: expandtab shiftwidth=4
