20 Commits

Author SHA1 Message Date
Nathan Harris aa185a0133 Lay groundwork for RESP3 support and flat ChannelHandler hierarchy
## Motivation

Since Redis 6.0, a new serialization protocol format (v3) is available that gives richer semantic reasoning behind the different types to enable commands to better understand the return types to provide in their programming language.

In addition, the `RESPTranslator` type is going to see more direct usage, and the current API doesn't make read well.

## Changes

- Add: Internal `RESPVersion` enum that the `RESPTranslator` will start to use
- Rename: `RESPTranslator.parseBytes` to `RESPTranslator.read(from:)`
2022-12-26 14:43:21 -06:00
Fabian Fett c366a16fe8 Explicitly depend on NIO modules 2022-12-01 13:46:41 +01:00
Nathan Harris 3c6713038d Refactor RedisCommand into a general use object
Motivation:

RediStack today represents a command as a temporary object for the purpose of writing to the channel.

While it is useful to have an object for that purpose, commands handled in this way require immediate execution
and aren't available for other purposes.

Commands can serve a better purpose as a lightweight object to support delayed execution,
so that pipeling as described in issue #63 could be possible.

Modifications:

- Add: `get` overloads for JSON codable interaction on `RedisClient`
- Add: New `RedisZRangeResultOption` type for better interactions with zrange operations that can optionally return scores
- Add: New `RedisHashFieldKey` for type-safety when working with Hash field keys much like `RedisKey`
- Change: A few API types from enums to structs for library evolution
- Change: `RedisCommandHandler` to operate on a tuple of `RESPValue, EventLoopPromise<RESPValue>` rather than `RedisCommand`
- Change: `RedisCommand` to be a generic struct with the keyword, arguments, and a transform closure to defer execution
- Change: Almost all `RedisClient` command extensions to be factory methods on `RedisCommand` instead
- Change: Many response types to be optional to avoid developers having to do `isNull` checks on their own
- Change: `RedisClient.send(command:arguments:)` to be generic with `send(_:)` as the signature
- Rename: RedisClient extensions for scan methods to be more discoverable and legible as `scanHashField`, etc.

Result:

It should be easier to support a clean pipelining API with deferred command execution,
with extensions being easier for 2nd party developers, and the maintenance overhead of all of the command extensions
should be a little lighter when it comes to changing HOW commands are sent such as adding a context parameter
2020-12-02 08:41:59 +00:00
Nathan Harris c4ff3fc300 Rename ELF extension map to tryConverting and make internal
Motivation:

As originally pointed out in #48, the `map` prefix alone is not enough context into what the method actually does.

Since it fails the future, and isn't a mapping function, the name should reflect this.

But, this method as-is currently provides little value outside of the client context, so it should not be `public`.

If users provide an adequate use case for having it outside of the package, then it can be made public again.

Modifications:

- Rename: ELF where Value == RESPValue extension `map` to `tryConverting`
- Change: `tryConverting` from public to internal

Result:

A "problematic" method should no longer be available for API users to hurt themselves with.
2020-06-20 23:58:45 -07:00
Nathan Harris b48b472930 Convert RESPTranslator.ParsingError from enum to struct
Motivation:

Much like the change for `RedisClientError` (6471a2) any error enum that we may want to change will necessitate a major SemVer.

To avoid this, we need to use the established struct-as-enum pattern.

Modifications:

- Change: `RESPTranslator.ParsingError` to be a struct backed by an enum

Result:

The library should be able to evolve to add more `RESPTranslator.ParsingError` cases without becoming a breaking change.
2020-06-04 16:40:01 +00:00
George Barnett 5749215e5d Replace usage of @_specialize with @inlinable
Motivation:

- Newer compilers warn that "'exported: true' has no effect in
  '_specialize' attribute"
- Specialize is underscored so (probably) shouldn't be relied upon
  outside of the stdlib

Modifications:

- Replace @_specialize with @inlinable

Result:

Fewer warnings on more recent compilers
2020-06-04 11:54:50 +01:00
Cory Benfield 2211dbf36b Detect and throw on invalid integer.
Motivation:

parseInteger did not distinguish between not having enough bytes for an
integer and not being able to parse the integer that was present. This
was a bit tricky for code internally, where some call sites had extra
code looking for spooky action at a distance in order to determine if
the integer failed to parse.

This is unnecessary: parseInteger is sufficiently aware of what's going
on to address this problem itself.

Modifications:

- Added a new parser error (acceptable as we haven't tagged 1.0 yet).
- Throw it from parseInteger if the integer is invalid.

Result:

parseInteger clearly communicates if the integer failed to parse.
2020-05-27 16:00:20 +00:00
Cory Benfield 638fbb0754 Clean up indexing of ByteBufferView
Motivation:

ByteBufferView is not zero indexed, but parseSimpleString assumes it is.

Modifications:

- Correctly compute on the distance between two indices.
- New, somewhat contrived, test case.

Result:

No functional change: because RediStack assumes the remote peer will
always correctly terminate with /r/n, there is no point at which this
code could misbehave in the current implementation. However, with small
changes it is possible to trigger it, as the new test demonstrates.
2020-05-27 10:44:38 +01:00
Cory Benfield f9579373f5 Avoid creating temporary arrays
Motivation:

When we only want the first byte, rather than create temporary
intermediate arrays we can just ask NIO to give us the first byte. This
avoids unnecessary allocations.

Modifications:

- Replace `readBytes(length: 1).first` with `readInteger(as:
  UInt8.self)`

Results:

11% performance improvement in load testing due to reduced allocator
pressure on the hot path.
2020-05-26 18:11:01 +00:00
Cory Benfield dd08685566 Avoid transient ByteBufferView
Motivation:

When attempting to locate a single byte, creating a transient
ByteBufferView is an excessively heavyweight operation compared to a
simple getInteger. In particular, a BBV requires retain/release
operations to enforce the CoW invariants, as well as requires jumps
through substantial amounts of generic Collection code. While this can
be specialized, so can getInteger, and getInteger has much less code in
the way to cause costs.

Modifications:

- Replace temporary view creation with getInteger.

Results:

5% performance improvement on raw throughput tests.
2020-05-26 15:43:11 +01:00
Nathan Harris c1ba671097 Reduce RESPValue initialization complexities and simplify RedisKey implementation
Motivation:

`RESPValue` exposes a fair amount of complexity on how to intialize a single instance with the various overloads.

This aims to simplify the complexity for developers by providing a single initializer and relying on `RESPValueConvertible` to handle the complexities.

In addition, the Swift complier synthesizes a lot of default conformances that `RedisKey` has manually written, which is just unnecessary code.

Modifications:

- Rename: `RESPValue.init(_:)` to `RESPValue.init(from:)`
- Change: `RESPValue.init` `String?` and `FixedWidthInteger` overloads from `public` to `internal`
- Remove: Unnecessary code for various protocol conformances for `RedisKey`

Result:

Developers should have a direct and guided way of making instances of `RESPValue`
2020-03-19 22:20:47 -07:00
Nathan Harris fb161021a9 Rename ELF.convertFromRESPValue to be an overload of map 2020-02-19 20:59:11 -08:00
Nathan Harris 6bd5df7d93 Add map overloads for casting RESPValue elements in a Collection 2020-02-19 20:44:27 -08:00
Nathan Harris 17fa5dad0a Cleanup Extensions by placing code in more appropriate places 2020-02-19 20:41:41 -08:00
Nathan Harris ea6f427993 Add type-safe representation of Redis keys
Motivation:

Inspired by Swift by Sundell's article on type-safe identifers, the goal of this commit is to have the compiler
assist in preventing incorrect Redis key values from being used in API calls.

See https://www.swiftbysundell.com/articles/type-safe-identifiers-in-swift/ for the inspiration.

Modifications:

- Add new `RedisKey` struct that wraps around a single `String` value that conforms to several expected protocols
  (Hashable, Comparable, Codable, etc.)
- Change all command APIs to require `RedisKey` rather than plain strings

Result:

When encountering an API requiring a RedisKey, it should be much more apparant at the use site what form a value should take.
2019-12-27 21:38:35 -08:00
Nathan Harris 479c024d4b Change RESPValue.init(bulk:) initializers to accept a wider range of values
Motivation:

While working to add more test coverage with `RESPTranslator`, it was made apparent that a `.bulkString(.none)` is impossible to create directly with the `RESPValue` initializers, even though it is a reasonable possibility.

Additionally, forcing all integer types to have to be stored in an `Int` is unnecessarily restrictive.

Modifications:

- Change `RESPValue.init(bulk:)` initializers to accept `Optional` instances
- Change `RESPValue.init(bulk:)` for `Int` initializer to be generic on `FixedWidthInteger`

Result:

Converting types to and from `RESPValue` should be more bi-directional and seamless.
2019-07-29 05:05:39 +00:00
Nathan Harris b9b703078e Add more test coverage of RESPTranslator
Motivation:

Diagnostics for why `.bulkString` parses might fail were weak, and edge cases fell through gaps in coverage were found.

Modifications:

Added new cases to `RESPTranslator.ParsingError` for `.bulkString` parsing with additional test coverage.

Result:

Users should have better diagnostics for bogus data or failed parsing state.
2019-07-28 21:42:43 -07:00
Nathan Harris d702121f59 Add Equatable conformance for RedisError
Motivation:

There is a reasonable way to compare if two `RedisErrors` are equal, which was seen as needed in the `Equatable` conformance for `RESPValue`.

Modifications:

Added `Equatable` conformance for `RedisError` by comparing the messages.

Result:

Two `RedisError` instances are now equatable.
2019-07-27 23:48:40 -07:00
Nathan Harris 081c7ca855 Add Equatable conformance to RESPValue
Motivation:

While working on unit tests the need for conformance to `Equatable` for `RESPValue` has been needed a few times and it was decided to make it public.

Modifications:

Added conformance to `Equatable` for `RESPValue` with unit test.

Result:

Users should now be able to compare two `RESPValue` instances for equality.
2019-07-27 23:25:03 -07:00
Nathan Harris 7e7e354697 61 -- Rebrand from RedisNIO to RediStack 2019-07-08 19:45:33 -07:00