aidantwoods / swift-paseto

Platform-Agnostic Security Tokens implementation in Swift
MIT License
23 stars 6 forks source link

Refactor into subclasses #2

Closed aidantwoods closed 6 years ago

aidantwoods commented 6 years ago

Implementation no longer defines both the public and local API, rather these are now dedicated protocols. We also remove Self requirements from this interface, and instead use a constrained associated type within the Local and Public protocols as a type parameter to Message. Implementation now only need define a Payload (this allows us to remove the type projection to an enum state in a switch case to define a single payload that behaves differently – instead we now have a dedicated type).

Since this is a tremendous refactor in breadth, there aren't really any good opportunities to break it up into stages for commits (hence the single commit). If we commit changes in parts then they don't really make sense with the rest of the codebase (since we're transitioning to a new API), and so they aren't really very valuable. IMO a single commit is best because allows one to follow code to its new home (rather than hunting around across different commits).

These changes require dropping Swift 4.0 support in favour of Swift 4.1 and up – in the Public and Local protocols the mentioned constrained associated type which refers to the implementation also need implement the Public or Local protocol itself. This allows us to have a subtype of the version write a base implementation, and have the version itself defer to it without needing to declare a payload itself. This is important because while we require a generalised implementation, we cannot have a version implement directly two separate base Public and Local implementations if these require a payload (because there is only one type name, but two payload types are required – one for the Local and one for the Public implementation).

Instead we separate the Local and Public API from their implementations Local & Implementation, Public & Implementation respectively. This also serves to ensure that if we would like to specify a message, we require an Implementation (e.g. Version2.Local and not just Version2 – even though Version2 implements Local, a version2 message is not specific enough). On the contrary, if we only wish to use the API of local we need not refer to Version2.Local.encrypt for example, rather we can instead use just Version2.encrypt since this method name is enough to defer us to the Local base implementation.

For sanity, we also require that any Local or Public defer to a base implementation by constraining our deferred implementation's deferred implementation to be identical to our deferred implementation (this ensures that deferral chains are at most of length 2, and are importantly not recursive (unless they are of length 1) which means will will always "settle" on a true base implementation).