Significant redesign of the underlying implementation. A few breaking changes but minor work to fix-up your code. Well worth it for the new features.
Can join different Endpoint collections.
Example: Have a main that branches off to multiple "sub-systems".
Now supports class methods as handlers.
Works great with first feature. Have a parent collection with static handlers, then attach instance handlers associated with a 'this' object.
Also, the instance object can instead be a callback function that returns the instance object. For example, a rest endpoint like /devices/<device-id>/control/start might use the device-id parameter to lookup and return the right device object instance to invoke the "start" endpoint handler.
(Mostly) less cryptic error messages
though I wish it was still better but the old ones might as well have been machine code.
Breaking Changes
As mentioned, all the endpoints change from endpoints.on("/path/etc", GET(func), PUT(func)) to endpoints.on("/path/etc").GET(func).PUT(func).
Endpoints class is just a collection of path nodes. The Node class has all the power with methods on(), resolve(), with(), GET, PUT, PATCH, etc. This shouldnt be a breaking change in most cases other than the first point.
Joining Endpoint collections
As mentioned in features, we can join two Endpoints collections using the .with(args) method. The with method will return the right-hand Endpoints collection so you can keep using .on(), .GET(), .PUT(), etc in a method invoke chain. I am using Restfully in another project (Nimble) where the Rest interface is key to interfacing with the internal sensor devices, etc. in a plug-and-play way so splitting the Rest endpoints into multiple units was very important.
Typically, you would use with() something like this:
Endpoints ep1, ep2; // this type is actually system dependent
In the above example the status and statistics endpoints are actually defined in the ep2 collection, then attached to the /api/sys node of the ep1 collection. The with() method always returns the right-hand Endpoints collection.
In the above examples both collections have the same handler type (static functions). We can also change the code to use method instances for ep2:
class Sys { status(); statistics() }
Sys mysys;
Endpoints ep1; // this type is actually system dependent
Endpoints<Sys> ep2;
ep1
.on("/api/sys")
.with(mysys, ep2)
.on("status")
.GET(&Sys::status)
.on("statistics")
.GET(&Sys::statistics)
Actually, ep2 is optional here since we never actually use ep2 outside the chain. The Endpoints class can construct it as an anonymous type and store it within the parent collection.
class Sys { status(); statistics() }
Sys mysys;
Endpoints ep1; // this type is actually system dependent
ep1
.on("/api/sys")
.with(mysys) // this creates a new anonymous collection with Sys class and returns it
.on("status")
.GET(&Sys::status)
.on("statistics")
.GET(&Sys::statistics)
The with(...) method has overloads that support these args:
<Endpoints> - join Endpoints at the current node. (both left and right Endpoints collections must be the same type.)
<object>, <Endpoints> - join Endpoints at current node, but use object as the 'this' pointer.
<object> - construct an anonymous Endpoints collection and store within the parent collection. The object is still used as the 'this' pointer during invokes.
<callback_function>, <Endpoints> - join Endpoints at current node. At Request time, the callback_function is invoked to lookup and return the object reference. callback_function prototype would be Klass cb_func(Rest::UriRequest& r) where Klass is your object type and UriRequest contains the URL and parameters parsed so far and your code may use a parameter as the key to lookup your object instance dynamically.
Class Method Handlers vs Static Handlers
In all cases, when you use with(...) to attach an object instance to a member function the this object is bound to the member function at Request time to produce a statically invokable function call (think std::bind). So in the above examples resolving from the parent (containing static handlers) will always return a static function handler regardless of joined instance member based Endpoints.
You can also create stand-alone Endpoints that return class-method handlers as well but all method handlers in an Endpoints collection must be from the same class type. You would resolve a URL to get a class-method handler, and would perform the invoke yourself using the .* operator.
Significant redesign of the underlying implementation. A few breaking changes but minor work to fix-up your code. Well worth it for the new features.
/devices/<device-id>/control/start
might use the device-id parameter to lookup and return the right device object instance to invoke the "start" endpoint handler.Breaking Changes
endpoints.on("/path/etc", GET(func), PUT(func))
toendpoints.on("/path/etc").GET(func).PUT(func)
.Joining Endpoint collections
As mentioned in features, we can join two Endpoints collections using the
.with(args)
method. The with method will return the right-hand Endpoints collection so you can keep using.on(), .GET(), .PUT(), etc
in a method invoke chain. I am using Restfully in another project (Nimble) where the Rest interface is key to interfacing with the internal sensor devices, etc. in a plug-and-play way so splitting the Rest endpoints into multiple units was very important.Typically, you would use with() something like this: Endpoints ep1, ep2; // this type is actually system dependent
or nicely indented to show endpoint level:
In the above example the status and statistics endpoints are actually defined in the ep2 collection, then attached to the /api/sys node of the ep1 collection. The with() method always returns the right-hand Endpoints collection.
In the above examples both collections have the same handler type (static functions). We can also change the code to use method instances for ep2:
Actually, ep2 is optional here since we never actually use ep2 outside the chain. The Endpoints class can construct it as an anonymous type and store it within the parent collection.
The
with(...)
method has overloads that support these args:<Endpoints>
- join Endpoints at the current node. (both left and right Endpoints collections must be the same type.)<object>, <Endpoints>
- join Endpoints at current node, but useobject
as the 'this' pointer.<object>
- construct an anonymous Endpoints collection and store within the parent collection. Theobject
is still used as the 'this' pointer during invokes.<callback_function>, <Endpoints>
- join Endpoints at current node. At Request time, the callback_function is invoked to lookup and return theobject
reference. callback_function prototype would beKlass cb_func(Rest::UriRequest& r)
where Klass is yourobject
type and UriRequest contains the URL and parameters parsed so far and your code may use a parameter as the key to lookup your object instance dynamically.Class Method Handlers vs Static Handlers
In all cases, when you use
with(...)
to attach an object instance to a member function thethis
object is bound to the member function at Request time to produce a statically invokable function call (think std::bind). So in the above examples resolving from the parent (containing static handlers) will always return a static function handler regardless of joined instance member based Endpoints.You can also create stand-alone Endpoints that return class-method handlers as well but all method handlers in an Endpoints collection must be from the same class type. You would resolve a URL to get a class-method handler, and would perform the invoke yourself using the .* operator.