flyingeinstein / Restfully

Arduino library for making Rest compliant APIs on the ESP8266 with embedded Uri arguments
MIT License
9 stars 2 forks source link

Feature/split endpoints #4

Closed guru-florida closed 5 years ago

guru-florida commented 5 years ago

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.

Breaking Changes

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

ep1.on("/api/sys").with(ep2).on("status").GET(status_func);

or nicely indented to show endpoint level:

ep1
    .on("/api/sys")
    .with(ep2)
        .on("status")
            .GET(status_func)
        .on("statistics")
            .GET(statistics_func)

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:

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.