diff --git a/.jazzy.yaml b/.jazzy.yaml deleted file mode 100644 index 8fac6a1..0000000 --- a/.jazzy.yaml +++ /dev/null @@ -1,5 +0,0 @@ -documentation: "{BinaryFormat,ProtobufSupport}.md" -author: 'Christoph Hagen' -author_url: 'https://christophhagen.de' -source_host: 'github' -source_host_url: 'https://github.com/christophhagen/BinaryCodable' diff --git a/BinaryFormat.md b/BinaryFormat.md index 59cb9df..c599385 100644 --- a/BinaryFormat.md +++ b/BinaryFormat.md @@ -1,176 +1,223 @@ # Binary data structure -**Note:** The `BinaryCodable` format is optimized for size, but does not go all-out to create the smallest binary sizes possible. +**This document describes the binary format of version 3. For version 2 and below, see [legacy format](LegacyFormat.md)** + +**Note:** The `BinaryCodable` format is optimized for size, but does not go all-out to create the smallest binary sizes possible. The binary format, while being efficient, needs to serve as a general-purpose encoding, which will never be as efficient than a custom format optimized for a very specific use case. -If this is your goal, then simply using `Codable` with it's key-value approach will not be the best solution. An unkeyed format optimized for the actually encoded data will be more suitable. But if you're really looking into this kind of efficiency, then you probably know this already. +If this is your goal, then simply using `Codable` with it's key-value approach will not be the best solution. +An unkeyed format optimized for the actually encoded data will be more suitable. +But if you're really looking into this kind of efficiency, then you probably know this already. + +The encoding format used by `BinaryCodable` has been designed to be efficient, while at the same time supporting all features of the `Codable` implementation. + +## Basic types + +The basic types known to `Codable` are listed in the following table: + +| Type | Length | Encoding | +| --- | --- | --- | +| Bool | 1 | false: `0x00`, true: `0x01` | +| Data | ? | As itself +| Double | 8 | IEEE Double representation, little endian | +| Float | 4 |IEEE Double representation, little endian | +| Int8 | 1 | [Little endian](#little-endian) | +| Int16 | 2 | [Little endian](#little-endian) | +| Int32 | 1-5 | Zig-zag variable-length encoding | +| Int64 | 1-9 | Zig-zag variable-length encoding | +| Int | 1-9 | Zig-zag variable-length encoding | +| String | ? | UTF-8 data | +| UInt8 | 1 | [Little endian](#little-endian) | +| UInt16 | 2 | [Little endian](#little-endian) | +| UInt32 | 1-5 | [Variable-length encoding](#variable-length-encoding) | +| UInt64 | 1-10 | [Variable-length encoding](#variable-length-encoding) | +| UInt | 1-10 | [Variable-length encoding](#variable-length-encoding) | + +`BinaryCodable` also adds the following alternative encodings using the [`FixedSize`](#fixed-size-integers) property wrapper: + +| Type | Length | Encoding | +| --- | --- | --- | +| FixedSize\ | 4 | [Little endian](#little-endian) | +| FixedSize\ | 8 | [Little endian](#little-endian) | +| FixedSize\ | 8 | [Little endian](#little-endian) | +| FixedSize\ | 4 | [Little endian](#little-endian) | +| FixedSize\ | 8 | [Little endian](#little-endian) | +| FixedSize\ | 8 | [Little endian](#little-endian) | + +`FixedSize` can be used as a property wrapper, to transparently change the encoding: -The encoding format used by `BinaryCodable` is similar to Google's [Protocol Buffers](https://developers.google.com/protocol-buffers) in some aspects, but provides much more flexibility regarding the different types which can be encoded, including the ability to encode `Optional`, `Set`, single values, multidimensional `Array`s, and more. +```swift +struct MyType: Codable { -## Integer encoding + @FixedSize + var userId: Int64 +} +``` -Integers are encoded with different strategies depending on their size. Smaller types, like `UInt8`, `Int8`, `UInt16`, and `Int16` are encoded using their binary representations in little-endian format. +### Boolean -Larger integers, like `UInt32`, `Int32`, `UInt64`, `Int64`, `Int`, and `UInt` are (by default) encoded using variable length zig-zag encoding, similar to [Protobuf signed integers](https://developers.google.com/protocol-buffers/docs/encoding#signed-ints). This means that smaller values are encoded as shorter binary representations, which is useful if integer values are mostly small. -**Note:** The `Varint` implementation is not equivalent to `Protobuf`, since `BinaryCodable` uses the last byte of a large integer directly, and thus encodes `Int.max` with 9 Byte instead of 10. This encoding is adapted when enforcing protobuf compatibility. +`Bool` values are encoded as a single byte, using `1` for `true`, and `0` for `false`. -The property wrapper [`FixedSize`](#fixed-size-integers) forces the values to be encoded using their little-endian representations. +### Little endian -## Floating-point types +Smaller integer types, like `UInt8`, `Int8`, `UInt16`, and `Int16` are encoded using their binary representations in little-endian format. +This format is chosen since most Apple architectures already store data as little-endian. -`Float` and `Double` values are encoded using their binary representations in little-endian format. +### Variable-length encoding -## Strings +Larger unsigned integers (`UInt32`, `UInt64` and `UInt`) are (by default) encoded using variable length encoding, similar to [Protobuf Base128 Varints](https://protobuf.dev/programming-guides/encoding/#varints). +This means that smaller values are encoded as shorter binary representations, which is useful if integer values are mostly small. -Swift `String` values are encoded using their `UTF-8` representations. If a string can't be encoded this way, then encoding fails. +**Note:** The `Varint` implementation is not equivalent to `Protobuf`, since `BinaryCodable` uses the last byte of a large integer directly (no continuation bit), and thus encodes `Int.max` with 9 Byte instead of 10. -## Booleans +### Zig-zag encoding -`Bool` values are always encoded as a single byte, using `1` for `true`, and `0` for `false`. +Larger signed integers (`Int32`, `Int64` and `Int`) are (by default) encoded using zig-zag encoding, similar to [Protobuf signed integers](https://developers.google.com/protocol-buffers/docs/encoding#signed-ints). +This format is more efficient for integers where the magnitude is small. -## Arrays +### Fixed-size integers -Arrays (and other sequences) are encoded by converting each item to binary data, and concatenating the results. Elements with variable length (like `String`) are prepended with their length encoded as a [Varint](#integer-encoding). +The property wrapper `FixedSize` forces the wrapped values to be encoded using their little-endian representations. +This is useful if the integer values are often large, e.g. for random numbers. -### Arrays of Optionals +### Floating-point types -It is possible to encode arrays where the elements are `Optional`, e.g. `[Bool?]`. -For all types with compiler-generated `Codable` conformance, an optional is prepended with one byte (`1` for the `.some()` case, `0` for `nil`). -If the optional has a value, then the encoded data follows the `1` byte. -If `nil` is encoded, then no additional data is added. -This means that an array of `[Bool?]` with the values `[true, nil, false]` is encoded as `[1, 1, 0, 1, 0]`. +`Float` and `Double` values are encoded using their binary representations in little-endian format. -- Note: One benefit of this encoding is that top-level sequences can be joined using their binary data, where `encoded([a,b]) | encoded([c,d]) == encoded([a,b,c,d])`. +### Strings -Custom implementations of `Encodable` and `Decodable` can directly call `encodeNil()` on `UnkeyedEncodingContainer` and `decodeNil()` on `UnkeyedDecodingContainer`. -This feature is not supported in the standard configuration and will result in a fatal error. -If these functions are needed, then the `prependNilIndexSetForUnkeyedContainers` must be set for the encoder and decoder. -If this option is set to `true`, then each unkeyed container is prepended with a "nil index set". -It first consists of the number of `nil` elements in the sequence (only those encoded using `encodeNil()`), encoded as a `Varint`. -Then follow the indices in the array where `nil` values are present, each encoded as a `Varint`. -The decoder can then first parse this `nil` set, and return the appropriate value for each position where a `nil` value is encoded when `decodeNil()` is called. -This approach is fairly efficient while only few `nil` values are encoded, or while the sequence doesn't contain a large number of elements. -For arrays that don't contain optionals, only a single byte (`0`) is prepended to the binary representation, to signal that there are no `nil` indices in the sequence. +Swift `String` values are encoded using their `UTF-8` representations. +If a string can't be encoded this way, then encoding fails. -More efficient ways could be devised to handle arrays of optionals, like specifying the number of `nil` or non-nil elements following one another, but the increased encoding and decoding complexity don't justify these gains in communication efficiency. +## Containers -## Structs +Every `Codable` structure is in some way constructed from just a the described basic types, which are arranged in three different containers: + +**Single value containers** can contain only a single element, or alternatively `nil`. -Structs are encoded using `Codable`'s `KeyedEncodingContainer`, which uses `String` or `Int` coding keys to distinguish the properties of the types. -By default, Swift uses the property names as `String` keys, which are used to encode each property as a key-value pair on the wire. -The first value is a `Varint`, which contains the length of the string key, plus additional information about the data associated with the key. -The bits 0-2 are used to signal the size value, and bit 3 of the `Varint` indicates whether the key is a string key (`1` = string, `0` = int). -The following data types are possible: +**Unkeyed containers** can store a sequence of different values (including `nil` values). -| Data type | Raw value | Protobuf | Swift types | Description | -| :---------------------- | :-------- | :---------------- | :------------------------------------------------------------------------------------- | -| `variableLengthInteger` | `0` | `varint`/`zigzag` | `Int`, `Int32`, `Int64`, `UInt`, `UInt32`, `UInt64` | A Base128 `Varint` using 1-9 bytes of data | -| `eightBytes` | `1` | `fixed64bit` | `Double`, `FixedSize`, `FixedSize`, `FixedSize`, `FixedSize` | A 64-bit float or integer in little-endian encoding. | -| `variableLength` | `2` | `delimited` | `String`, `Struct`, ... | The length of the data encoded as a `Varint` followed by `length` bytes | -| `fourBytes` | `5` | `fixed32bit` | `Float`, `FixedSize`, `FixedSize` | A 32-bit float or integer in little-endian encoding. | -| `byte` | `6` | - | `Bool`, `UInt8`, `Int8` | A single byte storing a number or boolean | -| `twoBytes` | `7` | - | `Int16`, `UInt16` | Two bytes storing an integer using little-endian format | +**Keyed containers** consist of values, which are associated with `CodingKey`s (either a String or an Int value). +Each of these containers can either encode [basic types](#basic-types), or include nested containers. +No matter what is encoded, each element is usually preceded by a length indicator, to show the decoder where the container ends. -With the four lower bits occupied by the data type and the string key indicator, the remaining bits are left to encode the length of the string key. +### Single Value Container encoding -For example, a property named `xyz` of type `UInt8` with value `123` would be encoded to the following: +Since a single value container can contain `nil`, it's encoding always requires a `nil` indicator. +At the top level, this is a single byte of either `1` to indicate `nil` (with no additional data folling it), or `0`, to indicate that the encoded value follows. +If a single value container is nested in some other container, then the `nil` indicator is contained in the length information that is prepended to the container data. -| Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | -| :----------------------------------------- | :--------- | :--------- | :--------- | :--------- | -| `0` `011` `1` `110` | `01111000` | `01111001` | `01111010` | `01111011` | -| Length `3`, `String` key, Data type `byte` | `x` | `y` | `z` | `123` | +### Unkeyed containers -### Integer keys +Unkeyed containers can contain values of different types in a sequence. +Each value (other containers or basic types) is encoded using its encoded data, prefixed by a length indicator to signal the decoder how many bytes are associated with each value. +This enables the decoder to split the data into separate chunks before the actual types are decoded. -The Swift `Codable` framework also provides the ability to specify integer keys for properties, which can significantly reduce the binary size. Integer keys can be assigned to properties by implementing custom `CodingKeys` for a type: -```swift -struct MyCodable: Codable { +### Length/nil indicators - let xyz: UInt8 +The length indicators for each encoded value in an unkeyed container consists of an unsigned integer, encoded using [variable-length encoding](#variable-length-encoding). +The length is not directly encoded as an integer, but shifted to the left by one bit, so that the LSB can be used as a `nil` indicator. +If the LSB is set, then `nil` is encoded for the value, and all other bits must be unset. +If the LSB is set to `0`, then the value is not `nil`, and the integer indicates the number of bytes used by the value's encoded data. - enum CodingKeys: Int, CodingKey { - case xyz = 2 - } -} +For example assume the following encoding routine: + +```swift +var container = encoder.unkeyedContainer() +try container.encode(false) +try container.encodeNil() +try container.encode("Hello") +try container.encode(Data()) ``` -Integer coding keys are encoded as `Varint` instead of the `String` key length. This results in the following encoding for the same example as before: -| Byte 0 | Byte 1 | -| :------------------------------------------- | :--------- | -| `0` `010` `0` `110` | `01111011` | -| Integer key `2`, `Int` key, Data type `byte` | `123` | +This yields the following binary representation (hex): -Evidently this is a significant improvement, especially for long property names. Note that while it is possible to specify any integer as the key (between 2^59 and -2^59), small, positive integers are the most efficient. +``` +02 00 01 14 48 65 6C 6C 6F 00 +``` -### Optional properties +which translates to: -Any properties of structs or other keyed containers is omitted from the binary data, i.e. nothing is encoded. -The absence of a key then indicates to the decoder that the value is `nil`. -For multiple optionals (e.g. `Bool??`), the inner optional is encoded as a `varLen` type, with the same encoding as optionals in arrays. +``` +0x02 // First element is not nil, length 1 +0x00 // First element data (Bool `false`) +0x01 // Second element is nil +0x14 // Third element is not nil, length 5 +0x48656C6C6F // Third element data, String "Hello" +0x00 // Fourth element is not nil, length 0 +``` -## Codable quirks +### Keyed containers -There are some interesting details on how `Codable` treats certain types, which produce unexpected binary data. Although `BinaryCodable` decodes all these cases correctly, implementing these "features" may be difficult on other platforms. +Keyed containers work similar to unkeyed containers, except that each element also has a key inserted before the element data. -### Enums with associated values +Keys can be of type `String` or `Int`. +For both types, and unsigned integer is encoded using [variable-length encoding](#variable-length-encoding). +If the key is an integer, then the LSB is set to `0`, and the remaining value (shifted by 1 to remove the LSB) indicates the value of the integer key. +So an integer key of `1` is encoded as `0x02`, a key of `2` as `0x04` and so on. +This process is very similar to the encoding of the length or `nil` described in the previous chapter. -Given an enum with associated values: +If the key is a string, then the LSB is set to `1`, and the remaining integer specifies the length of the string key. +The actual string is then appended in its [encoded form](#strings). +So a string key of "value" is encoded as: -```swift -enum MyEnum: Codable { - case one(String) - case two(Bool, Data) -} ``` - -The encoding will consist of: -- The enum case name as a String key -- A keyed container with: - - All associated values in the order of their definition, keyed by `"_0"`, `"_1"`, ... - -For example, the value - -```swift -let value = MyEnum.one("Some") +11 // String key, length 5 +118, 97, 108, 117, 101 // String "value" ``` -would be encoded as: +After the key data follows the actual element data, consisting (similar to unkeyed containers) of the length/nil indicator and the encoded value. -| Byte 0 | Byte 1 - 3 | Byte 4 | Byte 5 | Byte 6 - 7 | Byte 8 | Byte 9 - 12 | -| :------------------------------ | :--------------- | :--------- | :----------------- | :---------- | :--------- | :-------------------- | -| `0x3A` | `0x6F 0x6E 0x65` | `0x08` | `0x2A` | `0x5F 0x30` | `0x04` | `0x53 0x6E 0x64 0x08` | -| `String` key (Len 3), `VarLen` | `one` | Length `8` | String key (Len 2) | `_0` | Length `4` | `Some` | +### Basic example -Let's use the same example with integer keys: +Consider the following swift structure: ```swift -enum MyEnum: Codable { - case one(String) - case two(Bool, UInt8) +struct Message: Codable { + let isComplete: Bool + let owner: String? + let references: [Int] enum CodingKeys: Int, CodingKey { - case one = 1 - case two = 2 + case isComplete = 1 + case owner = 2 + case references = 3 } } ``` -Then, the value +Encode the message ```swift -let value = MyEnum.two(true, 123) +let message = Message( + isComplete: true + owner: "Bob" + references: [3, -280]) ``` -would be encoded as: - -| Byte 0 | Byte 1 | Byte 2 | Byte 3 - 4 | Byte 5 | Byte 6 | Byte 7 - 8 | Byte 9 | -| :----------------------- | :--------- | :------------------------- | :---------- | :----------- | :------------------------- | :---------- | :----------- | -| `0x22` | `0x08` | `0x2E` | `0x5F 0x30` | `0x01` | `0x2E` | `0x5F 0x31` | `0x7B` | -| `Int` key (2), `VarLen` | Length `8` | String key (Len 2), `Byte` | `_0` | `Bool(true)` | String key (Len 2), `Byte` | `_1` | `UInt8(123)` | - -Note: Since the associated values are encoded in a keyed container, there order in the binary data may be different, unless the `sortKeysDuringEncoding` option is set to `true`. - -### Special dictionaries +will give the following binary data: + +| Byte index | Value | Content | +|--- |--- |--- | +| 0 | 0x02 | CodingKey(stringValue: 'isComplete', intValue: 1) +| 1 | 0x02 | Length 1 +| 2 | 0x01 | Bool `true` +| 3 | 0x04 | CodingKey(stringValue: 'owner', intValue: 2) +| 4 | 0x06 | Length 3 +| 5-7 | 0x42 0x6f 0x62 | String "Bob" +| 8 | 0x06 | CodingKey(stringValue: 'references', intValue: 3) +| 9 | 0x0A | Length 5 +| 10 | 0x02 | Length 1 +| 11 | 0x06 | Int `3` +| 12 | 0x04 | Length 2 +| 13-14 | 0xAF 0x04 | Int `-280` + +There are a few things to note: +- The properties are all marked by their integer keys +- The elements in the `references` array are also preceded by a length indicator +- The top level keyed container has no length information, since it can be inferred from the length of the provided data + +### Dictionaries Most dictionaries are treated as `Unkeyed Containers` by `Codable`, and each key value pair is encoded by first encoding the key, followed by the value, thus creating the flat structure: @@ -179,7 +226,7 @@ Most dictionaries are treated as `Unkeyed Containers` by `Codable`, and each key #### Dictionaries with Integer keys -For all dictionaries, which use `Int` as the key, e.g. `[Int: String]`, `[Int: Int]`, or generally `[Int: ...]`, the encoding is done using a `Keyed` container, where each dictionary value is encoded using a `CodingKey` with an integer value. This results in a structure more resembling [struct encoding](#structs) with [integer keys](#integer-keys): +For all dictionaries using `Int` as the key, e.g. `[Int: String]`, `[Int: Int]`, or generally `[Int: ...]`, the encoding is done using a `Keyed` container, where each dictionary value is encoded using a `CodingKey` with an integer value. This results in a structure more resembling [struct encoding](#structs) with [integer keys](#integer-keys): | Byte(s) | Byte(s) | Byte(s) | Byte(s) | Byte(s) | Byte(s) | | :-------------------------- | :------ | :-------------------------- | :------ | :-------------------------- | :------ | @@ -220,15 +267,5 @@ For dictionaries with `String` keys (`[String: ...]`), the process is similar to ## Stream encoding -The encoding for data streams only differs from standard encoding in two key aspects. - -### Added length information - -Each top-level element is encoded as if it is part of an unkeyed container (which it essentially is), meaning that each element has the necessary length information prepended to determine it's size. -Only types with data type `variable length` have their length prepended using [varint](#integer-encoding) encoding. -This concerns `String` and `Data`, as well as complex types like structs and arrays, among others. - -### Optionals - -A single byte is prepended to each `Optional` element, where binary `0x01` is used to indicate a non-optional value, and `0x00` is used to signal an optional value. -`nil` values have no additional data, so each is encoded using one byte. +The encoding for data streams only differs from standard encoding in one key aspect. +Each top-level element is encoded as if it is part of an unkeyed container (which it essentially is), meaning that each element has the necessary length information prepended to determine its size. \ No newline at end of file diff --git a/LegacyFormat.md b/LegacyFormat.md new file mode 100644 index 0000000..59cb9df --- /dev/null +++ b/LegacyFormat.md @@ -0,0 +1,234 @@ +# Binary data structure + +**Note:** The `BinaryCodable` format is optimized for size, but does not go all-out to create the smallest binary sizes possible. +The binary format, while being efficient, needs to serve as a general-purpose encoding, which will never be as efficient than a custom format optimized for a very specific use case. +If this is your goal, then simply using `Codable` with it's key-value approach will not be the best solution. An unkeyed format optimized for the actually encoded data will be more suitable. But if you're really looking into this kind of efficiency, then you probably know this already. + +The encoding format used by `BinaryCodable` is similar to Google's [Protocol Buffers](https://developers.google.com/protocol-buffers) in some aspects, but provides much more flexibility regarding the different types which can be encoded, including the ability to encode `Optional`, `Set`, single values, multidimensional `Array`s, and more. + +## Integer encoding + +Integers are encoded with different strategies depending on their size. Smaller types, like `UInt8`, `Int8`, `UInt16`, and `Int16` are encoded using their binary representations in little-endian format. + +Larger integers, like `UInt32`, `Int32`, `UInt64`, `Int64`, `Int`, and `UInt` are (by default) encoded using variable length zig-zag encoding, similar to [Protobuf signed integers](https://developers.google.com/protocol-buffers/docs/encoding#signed-ints). This means that smaller values are encoded as shorter binary representations, which is useful if integer values are mostly small. +**Note:** The `Varint` implementation is not equivalent to `Protobuf`, since `BinaryCodable` uses the last byte of a large integer directly, and thus encodes `Int.max` with 9 Byte instead of 10. This encoding is adapted when enforcing protobuf compatibility. + +The property wrapper [`FixedSize`](#fixed-size-integers) forces the values to be encoded using their little-endian representations. + +## Floating-point types + +`Float` and `Double` values are encoded using their binary representations in little-endian format. + +## Strings + +Swift `String` values are encoded using their `UTF-8` representations. If a string can't be encoded this way, then encoding fails. + +## Booleans + +`Bool` values are always encoded as a single byte, using `1` for `true`, and `0` for `false`. + +## Arrays + +Arrays (and other sequences) are encoded by converting each item to binary data, and concatenating the results. Elements with variable length (like `String`) are prepended with their length encoded as a [Varint](#integer-encoding). + +### Arrays of Optionals + +It is possible to encode arrays where the elements are `Optional`, e.g. `[Bool?]`. +For all types with compiler-generated `Codable` conformance, an optional is prepended with one byte (`1` for the `.some()` case, `0` for `nil`). +If the optional has a value, then the encoded data follows the `1` byte. +If `nil` is encoded, then no additional data is added. +This means that an array of `[Bool?]` with the values `[true, nil, false]` is encoded as `[1, 1, 0, 1, 0]`. + +- Note: One benefit of this encoding is that top-level sequences can be joined using their binary data, where `encoded([a,b]) | encoded([c,d]) == encoded([a,b,c,d])`. + +Custom implementations of `Encodable` and `Decodable` can directly call `encodeNil()` on `UnkeyedEncodingContainer` and `decodeNil()` on `UnkeyedDecodingContainer`. +This feature is not supported in the standard configuration and will result in a fatal error. +If these functions are needed, then the `prependNilIndexSetForUnkeyedContainers` must be set for the encoder and decoder. +If this option is set to `true`, then each unkeyed container is prepended with a "nil index set". +It first consists of the number of `nil` elements in the sequence (only those encoded using `encodeNil()`), encoded as a `Varint`. +Then follow the indices in the array where `nil` values are present, each encoded as a `Varint`. +The decoder can then first parse this `nil` set, and return the appropriate value for each position where a `nil` value is encoded when `decodeNil()` is called. +This approach is fairly efficient while only few `nil` values are encoded, or while the sequence doesn't contain a large number of elements. +For arrays that don't contain optionals, only a single byte (`0`) is prepended to the binary representation, to signal that there are no `nil` indices in the sequence. + +More efficient ways could be devised to handle arrays of optionals, like specifying the number of `nil` or non-nil elements following one another, but the increased encoding and decoding complexity don't justify these gains in communication efficiency. + +## Structs + +Structs are encoded using `Codable`'s `KeyedEncodingContainer`, which uses `String` or `Int` coding keys to distinguish the properties of the types. +By default, Swift uses the property names as `String` keys, which are used to encode each property as a key-value pair on the wire. +The first value is a `Varint`, which contains the length of the string key, plus additional information about the data associated with the key. +The bits 0-2 are used to signal the size value, and bit 3 of the `Varint` indicates whether the key is a string key (`1` = string, `0` = int). +The following data types are possible: + +| Data type | Raw value | Protobuf | Swift types | Description | +| :---------------------- | :-------- | :---------------- | :------------------------------------------------------------------------------------- | +| `variableLengthInteger` | `0` | `varint`/`zigzag` | `Int`, `Int32`, `Int64`, `UInt`, `UInt32`, `UInt64` | A Base128 `Varint` using 1-9 bytes of data | +| `eightBytes` | `1` | `fixed64bit` | `Double`, `FixedSize`, `FixedSize`, `FixedSize`, `FixedSize` | A 64-bit float or integer in little-endian encoding. | +| `variableLength` | `2` | `delimited` | `String`, `Struct`, ... | The length of the data encoded as a `Varint` followed by `length` bytes | +| `fourBytes` | `5` | `fixed32bit` | `Float`, `FixedSize`, `FixedSize` | A 32-bit float or integer in little-endian encoding. | +| `byte` | `6` | - | `Bool`, `UInt8`, `Int8` | A single byte storing a number or boolean | +| `twoBytes` | `7` | - | `Int16`, `UInt16` | Two bytes storing an integer using little-endian format | + + +With the four lower bits occupied by the data type and the string key indicator, the remaining bits are left to encode the length of the string key. + +For example, a property named `xyz` of type `UInt8` with value `123` would be encoded to the following: + +| Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | +| :----------------------------------------- | :--------- | :--------- | :--------- | :--------- | +| `0` `011` `1` `110` | `01111000` | `01111001` | `01111010` | `01111011` | +| Length `3`, `String` key, Data type `byte` | `x` | `y` | `z` | `123` | + +### Integer keys + +The Swift `Codable` framework also provides the ability to specify integer keys for properties, which can significantly reduce the binary size. Integer keys can be assigned to properties by implementing custom `CodingKeys` for a type: +```swift +struct MyCodable: Codable { + + let xyz: UInt8 + + enum CodingKeys: Int, CodingKey { + case xyz = 2 + } +} +``` +Integer coding keys are encoded as `Varint` instead of the `String` key length. This results in the following encoding for the same example as before: + +| Byte 0 | Byte 1 | +| :------------------------------------------- | :--------- | +| `0` `010` `0` `110` | `01111011` | +| Integer key `2`, `Int` key, Data type `byte` | `123` | + +Evidently this is a significant improvement, especially for long property names. Note that while it is possible to specify any integer as the key (between 2^59 and -2^59), small, positive integers are the most efficient. + +### Optional properties + +Any properties of structs or other keyed containers is omitted from the binary data, i.e. nothing is encoded. +The absence of a key then indicates to the decoder that the value is `nil`. +For multiple optionals (e.g. `Bool??`), the inner optional is encoded as a `varLen` type, with the same encoding as optionals in arrays. + +## Codable quirks + +There are some interesting details on how `Codable` treats certain types, which produce unexpected binary data. Although `BinaryCodable` decodes all these cases correctly, implementing these "features" may be difficult on other platforms. + +### Enums with associated values + +Given an enum with associated values: + +```swift +enum MyEnum: Codable { + case one(String) + case two(Bool, Data) +} +``` + +The encoding will consist of: +- The enum case name as a String key +- A keyed container with: + - All associated values in the order of their definition, keyed by `"_0"`, `"_1"`, ... + +For example, the value + +```swift +let value = MyEnum.one("Some") +``` + +would be encoded as: + +| Byte 0 | Byte 1 - 3 | Byte 4 | Byte 5 | Byte 6 - 7 | Byte 8 | Byte 9 - 12 | +| :------------------------------ | :--------------- | :--------- | :----------------- | :---------- | :--------- | :-------------------- | +| `0x3A` | `0x6F 0x6E 0x65` | `0x08` | `0x2A` | `0x5F 0x30` | `0x04` | `0x53 0x6E 0x64 0x08` | +| `String` key (Len 3), `VarLen` | `one` | Length `8` | String key (Len 2) | `_0` | Length `4` | `Some` | + +Let's use the same example with integer keys: + +```swift +enum MyEnum: Codable { + case one(String) + case two(Bool, UInt8) + + enum CodingKeys: Int, CodingKey { + case one = 1 + case two = 2 + } +} +``` + +Then, the value + +```swift +let value = MyEnum.two(true, 123) +``` + +would be encoded as: + +| Byte 0 | Byte 1 | Byte 2 | Byte 3 - 4 | Byte 5 | Byte 6 | Byte 7 - 8 | Byte 9 | +| :----------------------- | :--------- | :------------------------- | :---------- | :----------- | :------------------------- | :---------- | :----------- | +| `0x22` | `0x08` | `0x2E` | `0x5F 0x30` | `0x01` | `0x2E` | `0x5F 0x31` | `0x7B` | +| `Int` key (2), `VarLen` | Length `8` | String key (Len 2), `Byte` | `_0` | `Bool(true)` | String key (Len 2), `Byte` | `_1` | `UInt8(123)` | + +Note: Since the associated values are encoded in a keyed container, there order in the binary data may be different, unless the `sortKeysDuringEncoding` option is set to `true`. + +### Special dictionaries + +Most dictionaries are treated as `Unkeyed Containers` by `Codable`, and each key value pair is encoded by first encoding the key, followed by the value, thus creating the flat structure: + +| Key 1 | Value 1 | Key 2 | Value 2 | Key 3 | Value 3 | +| :---- | :------ | :---- | :------ | :---- | :------ | + +#### Dictionaries with Integer keys + +For all dictionaries, which use `Int` as the key, e.g. `[Int: String]`, `[Int: Int]`, or generally `[Int: ...]`, the encoding is done using a `Keyed` container, where each dictionary value is encoded using a `CodingKey` with an integer value. This results in a structure more resembling [struct encoding](#structs) with [integer keys](#integer-keys): + +| Byte(s) | Byte(s) | Byte(s) | Byte(s) | Byte(s) | Byte(s) | +| :-------------------------- | :------ | :-------------------------- | :------ | :-------------------------- | :------ | +| `Int` Key(Key 1), Data type | Value 1 | `Int` Key(Key 2), Data type | Value 2 | `Int` Key(Key 3), Data type | Value 3 | + +For example, the following works: + +```swift +struct MyStruct: Codable { + let a: Int + let b: Int + let c: Int +} + +// Encode a dictionary +let input: [String: Int] = ["a" : 123, "b": 0, "c": -123456] +let data = try BinaryEncoder.encode(input) + +// Decode as struct +let decoded = try BinaryDecoder.decode(MyStruct.self, from: data) +``` + +It also works the other way round: +```swift +// Encode struct +let input = MyStruct(a: 123, b: 0, c: -123456) +let data = try BinaryEncoder.encode(input) + +// Decode as dictionary +let decoded: [String: Int] = try BinaryDecoder.decode(from: data) +``` + +Note that this only works for dictionaries with concrete `Encodable` values, e.g. `[String: Encodable]` won't work. + +#### Dictionaries with String keys + +For dictionaries with `String` keys (`[String: ...]`), the process is similar to the above, except with `CodingKey`s having the `stringValue` of the key. There is another weird exception though: Whenever a `String` can be represented by an integer (i.e. when `String(key) != nil`), then the corresponding `CodingKey` will have its `integerValue` also set. This means that for dictionaries with integer keys, there may be a mixture of integer and string keys present in the binary data, depending on the input values. But don't worry, `BinaryCodable` will also handle these cases correctly. + +## Stream encoding + +The encoding for data streams only differs from standard encoding in two key aspects. + +### Added length information + +Each top-level element is encoded as if it is part of an unkeyed container (which it essentially is), meaning that each element has the necessary length information prepended to determine it's size. +Only types with data type `variable length` have their length prepended using [varint](#integer-encoding) encoding. +This concerns `String` and `Data`, as well as complex types like structs and arrays, among others. + +### Optionals + +A single byte is prepended to each `Optional` element, where binary `0x01` is used to indicate a non-optional value, and `0x00` is used to signal an optional value. +`nil` values have no additional data, so each is encoded using one byte. diff --git a/Package.swift b/Package.swift index 01c65d6..2240f9c 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( targets: ["BinaryCodable"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.19.0"), + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") ], targets: [ .target( @@ -19,8 +19,7 @@ let package = Package( dependencies: []), .testTarget( name: "BinaryCodableTests", - dependencies: ["BinaryCodable", .product(name: "SwiftProtobuf", package: "swift-protobuf")], - exclude: ["Proto/TestTypes.proto"]), + dependencies: ["BinaryCodable"]), ], swiftLanguageVersions: [.v5] ) diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift index 19ff79a..067b630 100644 --- a/Package@swift-5.5.swift +++ b/Package@swift-5.5.swift @@ -9,17 +9,14 @@ let package = Package( name: "BinaryCodable", targets: ["BinaryCodable"]), ], - dependencies: [ - .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.19.0"), - ], + dependencies: [], targets: [ .target( name: "BinaryCodable", dependencies: []), .testTarget( name: "BinaryCodableTests", - dependencies: ["BinaryCodable", .product(name: "SwiftProtobuf", package: "swift-protobuf")], - exclude: ["Proto/TestTypes.proto"]), + dependencies: ["BinaryCodable"]), ], swiftLanguageVersions: [.v5] ) diff --git a/ProtobufSupport.md b/ProtobufSupport.md deleted file mode 100644 index d11dee4..0000000 --- a/ProtobufSupport.md +++ /dev/null @@ -1,254 +0,0 @@ -# Protocol Buffer Compatibility - -`BinaryCodable` provides limited compatibility to [Google Protocol Buffers](https://developers.google.com/protocol-buffers). Certain Swift types can be encoded to protobuf compatible binary data, and vice versa. The standard [binary format](BinaryFormat.md) is similar to protobuf, but includes some deviations to support all Swift types and features. There are additional `ProtobufEncoder` and `ProtobufDecoder` types which change the encoding format to be protobuf-compatible, at the expense of errors for unsupported features. - -For a description of the Protocol Buffer format, see the [official documentation](https://developers.google.com/protocol-buffers). - -**Important notes** -- Advanced protobuf features like message concatenation are not supported. -- Unsupported features of Protobuf *may* cause the encoding to fail with a `ProtobufEncodingError`. Interoperability should be thoroughly checked through testing. - -## Usage - -The conversion process is equivalent to the `BinaryEncoder` and `BinaryDecoder` types. - -```swift -import BinaryCodable -``` - -### Encoding - -Construct an encoder when converting instances to binary data, and feed the message(s) into it: - -```swift -let message: Message = ... - -let encoder = ProtobufEncoder() -let data = try encoder.encode(message) -``` - -### Decoding - -Decoding instances from binary data works much the same way: - -```swift -let decoder = ProtobufDecoder() -let message = try decoder.decode(Message.self, from: data) -``` - -Alternatively, the type can be inferred: - -```swift -let message: Message = try decoder.decode(from: data) -``` - -### Errors - -It is possible for both encoding and decoding to fail. -All possible errors occuring during encoding produce `BinaryEncodingError` or `ProtobufEncodingError` errors, while unsuccessful decoding produces `BinaryDecodingError` or `ProtobufDecodingError`. -All are enums with several cases describing the nature of the error. -See the documentation of the types to learn more about the different error conditions. - -## Message definition - -Protobuf organizes data into messages, which are structures with keyed fields. Compatible Swift types must be similar, so either `struct` or `class`. Simple types like `Int` or `Bool` are not supported on the root level, and neither are `Dictionary`, `Array` or `enum`. It's best to think of the proto definitions and construct Swift types in the same way. Let's look at an example from the [Protocol Buffer documentation](https://developers.google.com/protocol-buffers/docs/proto3#simple): - -```proto -message SearchRequest { - string query = 1; - int32 page_number = 2; - int32 result_per_page = 3; -} -``` - -The corresponding Swift definition would be: - -```swift -struct SearchRequest: Codable { - - var query: String - - var pageNumber: Int32 - - var resultPerPage: Int32 - - enum CodingKeys: Int, CodingKey { - case query = 1 - case pageNumber = 2 - case resultPerPage = 3 - } -} -``` - -The general structure of the messages is very similar, with the proto field numbers specified as integer coding keys. - -### Assigning integer keys - -The assignment of integer keys follow the same [rules](https://developers.google.com/protocol-buffers/docs/proto3#assigning_field_numbers) as for field numbers, just written out as an `enum` with `RawValue == Int` on the type conforming to `CodingKey`. The smallest field number you can specify is `1`, and the largest is `2^29 - 1`, or `536,870,911`. Codable types without (or with invalid) integer keys can't be encoded using `ProtobufEncoder` and will throw an error. - -### Scalar value types - -There are several [scalar types](https://developers.google.com/protocol-buffers/docs/proto3#scalar) defined for Protocol Buffers, which are the basic building blocks of messages. `BinaryCodable` provides Swift equivalents for each of them: - -| Protobuf primitive | Swift equivalent | Comment | -| :----------------- | :--------------------- | :-------------------------------------------------------------------- | -| `double` | `Double` | Always 8 byte | -| `float` | `Float` | Always 4 byte | -| `int32` | `Int32` | Uses variable-length encoding | -| `int64` | `Int64` | Uses variable-length encoding | -| `uint32` | `UInt32` | Uses variable-length encoding | -| `uint64` | `UInt64` | Uses variable-length encoding | -| `sint32` | `SignedInteger` | Uses ZigZag encoding, see [`SignedInteger` wrapper](#signed-integers) | -| `sint64` | `SignedInteger` | Uses ZigZag encoding, see [`SignedInteger` wrapper](#signed-integers) | -| `fixed32` | `FixedSize` | See [`FixedSize` wrapper](#fixed-size-integers) | -| `fixed64` | `FixedSize` | See [`FixedSize` wrapper](#fixed-size-integers) | -| `sfixed32` | `FixedSize` | See [`FixedSize` wrapper](#fixed-size-integers) | -| `sfixed64` | `FixedSize` | See [`FixedSize` wrapper](#fixed-size-integers) | -| `bool` | `Bool` | Always 1 byte | -| `string` | `String` | Encoded using UTF-8 | -| `bytes` | `Data` | Encoded as-is | -| `message` | `struct` | Nested messages are also supported. | -| `repeated` | `Array` | Scalar values must always be `packed` (the proto3 default) | -| `enum` | `Enum` | See [Enums](#enums) | -| `oneof` | `Enum` | See [OneOf Definition](#oneof) | - -The Swift types `Int8`, `UInt8`, `Int16`, and `UInt16` are **not** supported, and will result in an error. - -Note: `Int` and `UInt` values are always encoded as 64-bit numbers, despite the fact that they might be 32-bit values on some systems. Decoding a 64-bit value on a 32-bit system will result in an error. - -### Property wrappers - -The Protocol Buffer format provides several different encoding strategies for integers to minimize the binary size depending on the encoded values. By default, all integers are encoded using [Base 128 Varints](https://developers.google.com/protocol-buffers/docs/encoding#varints), but this can be changed using Swift `PropertyWrappers`. The following encoding options exist: - -| Swift type | [Varint encoding](https://developers.google.com/protocol-buffers/docs/encoding#varints) | [ZigZag Encoding](https://developers.google.com/protocol-buffers/docs/encoding#signed-ints) | [Fixed-size encoding](https://developers.google.com/protocol-buffers/docs/encoding#non-varint_numbers) | -| :--------- | :-------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------- | -| `Int32` | `Int32` | `SignedInteger` | `FixedSize` | -| `Int64` | `Int64` | `SignedInteger` | `FixedSize` | -| `UInt32` | `UInt32` | - | `FixedSize` | -| `UInt64` | `UInt64` | - | `FixedSize` | - -#### Fixed size integers - -While varints are efficient for small numbers, their encoding introduces a storage and computation penalty when the integers are often large, e.g. for random numbers. `BinaryCodable` provides the `FixedSize` wrapper, which forces integers to be encoded using their little-endian binary representations. This means that e.g. an `Int32` is always encoded as 4 byte (instead of 1-5 bytes using Varint encoding). This makes 32-bit `FixedSize` types more efficient than `Varint` if values are often larger than `2^28` (`2^56` for 64-bit types). - -Use the property wrapper within a `Codable` definition to enforce fixed-width encoding for a property: -```swift -struct MyStruct: Codable { - - /// Always encoded as 4 bytes - @FixedSize - var largeInteger: Int32 -} -``` - -The `FixedSize` wrapper is available to all `Varint` types: `Int`, `UInt`, `Int32`, `UInt32`, `Int64`, and `UInt64`. - -#### Signed integers - -Integers are by default [encoded as `Varint` values](BinaryFormat.md#integer-encoding), which is efficient while numbers are small and positive. For numbers which are mostly or also often negative, it is more efficient to store them using `Zig-Zag` encoding. `BinaryCodable` offers the `SignedValue` wrapper that can be applied to `Int`, `Int32` and `Int64` properties to increase the efficiency for negative values. - -Whenever your integers are expected to be negative, then you should apply the wrapper: -```swift -struct MyStruct: Codable { - - /// More efficiently encodes negative numbers - @SignedValue - var count: Int -} -``` - -### Enums - -Protocol Buffer [enumerations](https://developers.google.com/protocol-buffers/docs/proto3#enum) are supported, with a few notable caveats. Here is the example from the official documentation: -```proto -message SearchRequest { - - ... - - enum Corpus { - UNIVERSAL = 0; - WEB = 1; - IMAGES = 2; - LOCAL = 3; - NEWS = 4; - PRODUCTS = 5; - VIDEO = 6; - } - Corpus corpus = 4; -} -``` -The `BinaryCodable` Swift equivalent would be: - -```swift -struct SearchRequest: Codable { - - ... - - enum Corpus: Int, Codable { - case universal = 0 - case web = 1 - case images = 2 - case local = 3 - case news = 4 - case products = 5 - case video = 6 - } - - var corpus: Corpus - - enum CodingKeys: Int, CodingKey { - case corpus = 4 - } -} -``` - -It should be noted that protobuf enums require a default key `0`. - -### Oneof - -The protobuf feature [Oneof](https://developers.google.com/protocol-buffers/docs/proto3#oneof) can also be supported using a special enum definition. Given the protobuf definition (from [here](https://github.com/apple/swift-protobuf/blob/main/Documentation/API.md#oneof-fields)): - -```proto -syntax = "proto3"; -message ExampleOneOf { - int32 field1 = 1; - oneof alternatives { - int64 id = 2; - string name = 3; - } -} -``` - -The corresponding Swift definition would be: - -```swift -struct ExampleOneOf: Codable { - - let field1: Int32 - - // The oneof field - let alternatives: Alternatives - - // The OneOf definition - enum Alternatives: Codable, ProtobufOneOf { - case id(Int64) - case name(String) - - // Field values, must not overlap with `ExampleOneOf.CodingKeys` - enum CodingKeys: Int, CodingKey { - case id = 2 - case name = 3 - } - } - - enum CodingKeys: Int, CodingKey { - case field1 = 1 - // The field id of the Oneof field is not used - case alternatives = 123456 - } - } - ``` - -Note that the `Alternatives` enum must conform to `ProtobufOneOf`, which changes the encoding to create compatibility with the Protobuf binary format. - -**Important** The `ProtobufOneOf` protocol must not be applied to any other types, or encoding/decoding will fail. diff --git a/README.md b/README.md index 611b3fa..793e137 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

-This package provides convenient encoding and decoding to/from binary data for all Swift `Codable` types. It also provides limited cross-compatibility to [Google Protocol Buffers](https://developers.google.com/protocol-buffers). +This package provides convenient encoding and decoding to/from binary data for all Swift `Codable` types. ## Use cases @@ -22,8 +22,6 @@ One very popular alternative for binary data are Google's [Protocol Buffers](htt So if you're looking for a decently efficient binary encoder in a pure Swift project, then `BinaryCodable` may be right for you. Simply make your `struct`s (or classes!) conform to `Codable`, and `BinaryCodable` does the rest! -The [message format](#binary-format) is similar to that of `Protocol Buffers` (with some additions to support more types). It is possible to create [limited compatibility](#protocol-buffer-compatibility) between the two formats to exchange data with systems that don't support Swift. - ### Alternatives #### [Protocol Buffers](https://developers.google.com/protocol-buffers) @@ -45,10 +43,7 @@ Encoding according to the [BSON specification](https://bsonspec.org). Less effic Simply include in your `Package.swift`: ```swift dependencies: [ - .package( - name: "BinaryCodable", - url: "https://github.com/christophhagen/BinaryCodable", - from: "1.0.0") + .package(url: "https://github.com/christophhagen/BinaryCodable", from: "3.0.0") ], targets: [ .target(name: "MyTarget", dependencies: [ @@ -110,20 +105,34 @@ Alternatively, the type can be inferred: let message: Message = try decoder.decode(from: data) ``` -### Limitations +### Custom encoding and decoding + +`BinaryCodable` supports the use of custom encoding and decoding routines by implementing `encode(to:)` and `init(from:)`. + +There is only one aspect that's handled differently than the `Codable` documentation specifies, which is the explicit encoding of `nil` in keyed containers. +Calling `encodeNil(forKey:)` on a keyed container has no effect, there is no explicit `nil` value encoded for the key. +This results in the `contains()` function during decoding returning `false` for the key. +This is different to e.g. JSON, where calling `encodeNil(forKey:)` would cause the following encoding: -`BinaryCodable` supports most features of the `Codable` protocol. -One known issue is the missing compatibility with `SIMD` types (`import simd`), wich require the `count` property of unkeyed containers. -This optional property *can* return the number of values in the container. -Due to the binary format of `BinaryCodable`, this information is not available for unkeyed containers. -Only once specific types are decoded (e.g. using `decode(Bool.self)`) does the container know how to treat the data. -So a container with 8 bytes of data could contain 8 `Bool` values, or a single `Double`. +```json +{ + "myProperty" : nil +} +``` + +The implementation of `encodeNil(forKey:)` and `decodeNil(forKey:)` handles this case differently, because the alternatives are not optimal: +It would be possible to explicitly encode `nil` for a key, but this would cause problems with double optionals in structs (e.g. Int??), which could no longer distinguish between `.some(nil)` and `nil`. +To fix this issue, an additional `nil` indicator would be needed for **all** values in keyed containers, which would decrease the efficiency of the binary format. +That doesn't seem reasonable just to support a rarely used feature, since `encodeNil(forKey:)` is never called for automatically synthesized Codable conformances. + +The recommendation therefore is to use `encodeIfPresent(_, forKey:)` and `decodeIfPresent(_, forKey:)`. +Another option would be to use a double optional, since this is basically the information `encodeNil` provides: `nil`, if the key is not present, `.some(nil)`, if the key is present with `nil`, and `value`, if the key is present with a value. ### Errors -It is possible for both encoding and decoding to fail. -All possible errors occuring during encoding produce `EncodingError` errors, while unsuccessful decoding produces `DecodingError`s. -Both are the default Errors provided by Swift, supplied with information describing the nature of the error. +It's possible for both encoding and decoding to fail. +Encoding can produce `EncodingError` errors, while unsuccessful decoding produces `DecodingError`s. +Both are the default Errors provided by Swift, supplied with additional information describing the nature of the error. See the documentation of the types to learn more about the different error conditions. #### Handling corrupted data @@ -132,19 +141,22 @@ The [binary format](BinaryFormat.md) provides no provisions to detect data corru Additional external measures (checksums, error-correcting codes, ...) should be applied if there is an increased risk of data corruption. As an example, consider the simple encoding of a `String` inside a `struct`, which consists of a `key` followed by the length of the string in bytes, and the string content. -The length of the string is encoded using variable-length encoding, so a single bit flip (in the MSB of the length byte) could result in a very large `length` being decoded, causing the decoder to wait for a very large number of bytes to decode the string. +The length of the string is encoded using variable-length encoding, so a single bit flip (in the MSB of the length byte) could result in a very large `length` being decoded, causing the decoder to wait for a very large number of bytes to decode the string. This simple error would cause much data to be skipped, potentially corrupting the data stream indefinitely. At the same time, it is not possible to determine *with certainty* where the error occured, making error recovery difficult without additional information about boundaries between elements. -The decoding errors provided by the library are therefore only hints about error likely occuring from non-conformance to the binary format or version incompatibility, which are not necessarily the *true* causes of the failures when data corruption is present. +The decoding errors provided by the library are therefore only hints about errors likely occuring from non-conformance to the binary format or version incompatibility, which are not necessarily the *true* causes of the failures when data corruption is present. ### Coding Keys -The `Codable` protocol uses [`CodingKey`](https://developer.apple.com/documentation/swift/codingkey) definitions to identify properties of instances. By default, coding keys are generated using the string values of the property names. +The `Codable` protocol uses [`CodingKey`](https://developer.apple.com/documentation/swift/codingkey) definitions to identify properties of instances. +By default, coding keys are generated using the string values of the property names. Similar to JSON encoding, `BinaryCodable` can embed the property names in the encoded data. -Unlike JSON (which is human-readable), the binary representation produced by `BinaryCodable` is intended for cases when efficient encoding is important. `Codable` allows the use of integer keys for each property, which significantly increases encoding efficiency. You can specify integer keys by adding an `Int` enum conforming to the `CodingKey` protocol to the `Codable` type: +Unlike JSON (which is human-readable), the binary representation produced by `BinaryCodable` is intended for cases when efficient encoding is important. +`Codable` allows the use of integer keys for each property, which significantly increases encoding efficiency. +You can specify integer keys by adding an `Int` enum conforming to the `CodingKey` protocol to the `Codable` type: ```swift struct Message: Codable { @@ -165,19 +177,24 @@ struct Message: Codable { ``` The enum must have a raw value of either `Int` or `String`, and the cases must match the property names within the type (it is possible to omit keys for properties which should not be encoded). -Using integer keys can significantly decrease the binary size, especially for long property names. Additionally, integer keys can be useful when intending to store the binary data persistently. Changes to property names can be performed in the code without breaking the decoding of older data (although this can also be achieved with custom `String` keys). +Using integer keys can significantly decrease the binary size, especially for long property names. +Additionally, integer keys can be useful when intending to store the binary data persistently. +Changes to property names can be performed in the code without breaking the decoding of older data (although this can also be achieved with custom `String` keys). Notes: - Small, positive integer keys produce the smallest binary sizes. - The `0` integer key shouldn't be used, since it is also used internally when encoding `super`. -- Negative values for integer keys are **not** recommended (but possible). Since the keys are encoded as `Varint`, they are very inefficient for negative numbers. -- The allowed range for integer keys is from `-576460752303423488` (`-2^59`, inclusive) to `576460752303423487` (`2^59-1`, inclusive). Values outside of these bounds will cause a `fatalError` crash. +- Negative values for integer keys are **not** rsupported. +- The allowed range for integer keys is from `0` (inclusive) to `Int64.max` (inclusive). ### Property wrappers #### Fixed size integers -While varints are efficient for small numbers, their encoding introduces a storage and computation penalty when the integers are often large, e.g. for random numbers. `BinaryCodable` provides the `FixedSize` wrapper, which forces integers to be encoded using their little-endian binary representations. This means that e.g. an `Int32` is always encoded as 4 byte (instead of 1-5 bytes using Varint encoding). This makes 32-bit `FixedSize` types more efficient than `Varint` if values are often larger than `2^28` (`2^56` for 64-bit types). +While varints are efficient for small numbers, their encoding introduces a storage and computation penalty when the integers are often large, e.g. for random numbers. +`BinaryCodable` provides the `FixedSize` wrapper, which forces integers to be encoded using their little-endian binary representations. +This means that e.g. an `Int32` is always encoded as 4 byte (instead of 1-5 bytes using Varint encoding). +This makes 32-bit `FixedSize` types more efficient than `Varint` if values are often larger than `2^28` (`2^56` for 64-bit types). Use the property wrapper within a `Codable` definition to enforce fixed-width encoding for a property: ```swift @@ -190,31 +207,20 @@ While varints are efficient for small numbers, their encoding introduces a stora ``` The `FixedSize` wrapper is available to all `Varint` types: `Int`, `UInt`, `Int32`, `UInt32`, `Int64`, and `UInt64`. - - #### Other property wrappers - - There is an additional `SignedValue` wrapper, which is only useful when encoding in [protobuf-compatible format](ProtobufSupport.md#signed-integers). ### Options #### Sorting keys -The `BinaryEncoder` provides the `sortKeysDuringEncoding` option, which forces fields in "keyed" containers, such as `struct` properties (and some dictionaries), to be sorted in the binary data. This sorting is done by using either the [integer keys](#coding-keys) (if defined), or the property names. Dictionaries with `Int` or `String` keys are also sorted. +The `BinaryEncoder` provides the `sortKeysDuringEncoding` option, which forces fields in "keyed" containers, such as `struct` properties (and some dictionaries), to be sorted in the binary data. +This sorting is done by using either the [integer keys](#coding-keys) (if defined), or the property names. +Dictionaries with `Int` or `String` keys are also sorted. -Sorting the binary data does not influence decoding, but introduces a computation penalty during encoding. It should therefore only be used if the binary data must be consistent across multiple invocations. +Sorting the binary data does not influence decoding, but introduces a computation penalty during encoding. +It should therefore only be used if the binary data must be consistent across multiple invocations. -**Note:** The `sortKeysDuringEncoding` option does not *neccessarily* guarantee deterministic binary data, and should be used with care. - -#### Encoding optionals in arrays - -Sequences of `Optional` values (like arrays, sets, ...) are normally encoded one additional byte to indicate following value (`0x01`) or a `nil` value (`0x00`). -This works well for all compiler-generated conformances to `Codable`. -For custom implementations of `func encode(to: Encoder)` and `init(from: Decoder)`, the `encodeNil()` is *not* supported on `UnkeyedEncodingContainer`s. -If you must use this option, then it's necessary to enable the `prependNilIndexSetForUnkeyedContainers` option on both `BinaryEncoder` and `BinaryDecoder`. -Optional values are then encoded using a *nil index set*. -The index of each `nil` element in the sequence is recorded, and only non-nil values are encoded. -The indices of `nil` elements are then prepended to the data as an array of integers. -During decoding, this index set is checked to place `nil` values between the non-nil elements at the appropriate indices. +**Note:** The `sortKeysDuringEncoding` option does **not** guarantee deterministic binary data, and should be used with care. +Elements of any non-ordered types (Sets, Dictionaries) will appear in random order in the binary data. ### Stream encoding and decoding @@ -271,17 +277,57 @@ try decoder.read { element in There is also the possibility to read all elements at once using `readAll()`, or to read only one element at a time (`readElement()`). -### Protocol Buffer compatibility - -Achieving Protocol Buffer compatibility is described in [ProtobufSupport.md](ProtobufSupport.md). - ## Binary format To learn more about the encoding format, see [BinaryFormat.md](BinaryFormat.md). +## Legacy versions and migration + +Version 3 of `BinaryCodable` has significantly changed the binary format, which means that the two versions are **not** cross-compatible. +The [format](BinaryFormat.md) was changed to provide support for all `Codable` features, which was not possible with the previous format, that was adapted from [Protocol Buffers](https://developers.google.com/protocol-buffers). +The redesign of the library also reduced code complexity and size, which lead to some speed improvements and greater reliability. + +The support for interoperability with [Protocol Buffers](https://developers.google.com/protocol-buffers) was also dropped, since the binary formats are no longer similar. +The functionality was extracted to a separate library called [ProtobufCodable](https://github.com/christophhagen/ProtobufCodable). + +### Migrating from 2.x to 3.0 + +To convert data from the [legacy format](LegacyFormat) to the new version, the data has to be decoded with version 2 and re-encoded with version 3. +The Swift Package Manager currently doesn't allow to include the same dependency twice (with different versions), so the legacy version has been stripped down to the essentials and is provided as the stand-alone package [LegacyBinaryCodable](https://christophhagen.de/LegacyBinaryCodable). +It only allows decoding, and can be integrated as a separate dependency: + +```swift +dependencies: [ + .package(url: "https://github.com/christophhagen/BinaryCodable", from: "3.0.0"), + .package(url: "https://github.com/christophhagen/LegacyBinaryCodable", from: "2.0.0"), + +], +targets: [ + .target(name: "MyTarget", dependencies: [ + .product(name: "BinaryCodable", package: "BinaryCodable"), + .product(name: "LegacyBinaryCodable", package: "LegacyBinaryCodable") + ]) +] +``` + +In the code, you can then decode and re-encode: + +```swift +import BinaryCodable +import LegacyBinaryCodable + +func reencode(data: Data, as type: T.Type) throws -> Data where T: Codable { + let decoder = LegacyBinaryDecoder() + let value = try decoder.decode(T.self, from data: Data) + let encoder = BinaryEncoder() + return try encoder.encode(value) +} +``` + ## Tests -The library comes with an extensive test suite, which checks that encoding works correctly for many cases. These tests can be executed using ```swift test``` from the package root, or when opening the package using Xcode. +The library comes with an extensive test suite, which checks that encoding works correctly for many cases. +These tests can be executed using ```swift test``` from the package root, or when opening the package using Xcode. ## License @@ -289,12 +335,15 @@ MIT. See [License.md](License.md) ## Roadmap -### Generate protobuf definitions +### Additional tests -It should be possible to generate a string containing a working Protobuf definition for any type that is determined to be Protobuf compatible. +While the test suite covers many cases, there is no complete code coverage. +Especially the bahaviour in error conditions can use additional testing to ensure that there are no edge cases where the program crashes, or does some other weird thing. ### Speed +One option could be to use a common data storage during encoding and decoding, so that the individual containers can be converted to `struct`s to make them more lightweight. It may also be possible to prevent some unnecessary data copying. + Increasing the speed of the encoding and decoding process is not a huge priority at the moment. If you have any pointers on how to improve the performance further, feel free to contribute. diff --git a/Sources/BinaryCodable/BinaryDecoder.swift b/Sources/BinaryCodable/BinaryDecoder.swift index 49a6a3c..ecac131 100644 --- a/Sources/BinaryCodable/BinaryDecoder.swift +++ b/Sources/BinaryCodable/BinaryDecoder.swift @@ -1,7 +1,7 @@ import Foundation /** - An encoder to convert binary data back to `Codable` objects. + A decoder to convert data encoded with `BinaryEncoder` back to a `Codable` types. To decode from data, instantiate a decoder and specify the type: ``` @@ -18,44 +18,20 @@ import Foundation ``` let message = try BinaryDecoder.decode(Message.self, from: data) ``` - - Note: A single decoder can be used to decode multiple messages. + - Note: A single decoder can be used to decode multiple objects. */ -public final class BinaryDecoder { +public struct BinaryDecoder { /** Any contextual information set by the user for decoding. This dictionary is passed to all containers during the decoding process. */ - public var userInfo = [CodingUserInfoKey : Any]() + public var userInfo: [CodingUserInfoKey : Any] = [:] /** - Assumes that unkeyed containers are encoded using a set of indices for `nil` values. - - Refer to the ``prependNilIndexSetForUnkeyedContainers`` property of ``BinaryEncoder`` - for more information about the binary data format in both cases. - - - Note: This option defaults to `false` - - Note: To decode successfully, the encoder must use the same setting for `prependNilIndexSetForUnkeyedContainers`. - */ - public var containsNilIndexSetForUnkeyedContainers: Bool = false - - /** - The info for decoding. - - Combines the info data provided by the user with the internal keys of the decoding options. - */ - private var fullInfo: [CodingUserInfoKey : Any] { - var info = userInfo - if containsNilIndexSetForUnkeyedContainers { - info[CodingOption.prependNilIndicesForUnkeyedContainers.infoKey] = true - } - return info - } - - /** - Create a new binary encoder. - - Note: A single decoder can be used to decode multiple messages. + Create a new decoder. + - Note: A single decoder can be reused to decode multiple objects. */ public init() { @@ -69,21 +45,12 @@ public final class BinaryDecoder { - Throws: Errors of type `DecodingError` */ public func decode(_ type: T.Type = T.self, from data: Data) throws -> T where T: Decodable { - let root = DecodingNode(data: data, path: [], info: fullInfo) - return try type.init(from: root) - } - - /** - Decode a type from a data stream. - - This function is the pendant to `encodeForStream()` on ``BinaryEncoder``, and decodes a type from a data stream. - The additional length information added to the stream is used to correctly decode each element. - - Note: This function is not exposed publicly to keep the API easy to understand. - Advanced features like stream decoding are handled by ``BinaryStreamDecoder``. - */ - func decode(_ type: T.Type = T.self, fromStream provider: BinaryStreamProvider) throws -> T where T: Decodable { - let root = DecodingNode(decoder: provider, path: [], info: fullInfo) - return try type.init(from: root) + // Directly decode primitives, otherwise it would be decoded with a nil indicator + if let BaseType = T.self as? DecodablePrimitive.Type { + return try BaseType.init(data: data, codingPath: []) as! T + } + let node = try DecodingNode(data: data, parentDecodedNil: false, codingPath: [], userInfo: userInfo) + return try T.init(from: node) } /** diff --git a/Sources/BinaryCodable/BinaryEncoder.swift b/Sources/BinaryCodable/BinaryEncoder.swift index 75af383..7b1e112 100644 --- a/Sources/BinaryCodable/BinaryEncoder.swift +++ b/Sources/BinaryCodable/BinaryEncoder.swift @@ -14,12 +14,15 @@ import Foundation - Note: An ecoder can be used to encode multiple messages. */ -public final class BinaryEncoder { +public struct BinaryEncoder { + + /// The user info key for the ``sortKeysDuringEncoding`` option. + public static let userInfoSortKey: CodingUserInfoKey = .init(rawValue: "sortByKey")! /** Sort keyed data in the binary representation. - Enabling this option causes all keyed data (e.g. `Dictionary`, `Struct`) to be sorted by their keys before encoding. + Enabling this option causes all data in keyed containers (e.g. `Dictionary`, `Struct`) to be sorted by their keys before encoding. This option can enable deterministic encoding where the binary output is consistent across multiple invocations. - Warning: Output will not be deterministic when using `Set`, or `Dictionary` where `Key` is not `String` or `Int`. @@ -28,82 +31,66 @@ public final class BinaryEncoder { This option has no impact on decoding using `BinaryDecoder`. - Enabling this option will add the `CodingUserInfoKey(rawValue: "sort")` to the `userInfo` dictionary. + Enabling this option will add the `CodingUserInfoKey(rawValue: "sortByKey")` to the `userInfo` dictionary. + This key is also available as ``userInfoSortKey`` - Note: The default value for this option is `false`. */ - public var sortKeysDuringEncoding: Bool = false - - /** - Add a set of indices for `nil` values in unkeyed containers. - - This option is only necessary for custom implementations of `func encode(to:)`, when using `encodeNil()` on `UnkeyedEncodingContainer`. - - If this option is set to `true`, then the encoded binary data first contains a list of indexes for each position where `nil` is encoded. - After this data the remaining (non-nil) values are added. - If this option is `false`, then each value is prepended with a byte `1` for non-nil values, and a byte `0` for `nil` values. - - An index set is encoded using first the number of elements, and then each element, all encoded as var-ints. - - One benefit of this option is that top-level sequences can be joined using their binary data, where `encoded([a,b]) | encoded([c,d]) == encoded([a,b,c,d])`. - - - Note: This option defaults to `false` - - Note: To decode successfully, the decoder must use the same setting for `containsNilIndexSetForUnkeyedContainers`. - - Note: Using `encodeNil()` on `UnkeyedEncodingContainer` without this option results in a fatal error. - */ - public var prependNilIndexSetForUnkeyedContainers: Bool = false - - /** - Any contextual information set by the user for encoding. - - This dictionary is passed to all containers during the encoding process. - - Contains also keys for any custom options set for the encoder. - See `sortKeysDuringEncoding`. - */ - public var userInfo = [CodingUserInfoKey : Any]() - - /** - The info for encoding. - - Combines the info data provided by the user with the internal keys of the encoding options. - */ - private var fullInfo: [CodingUserInfoKey : Any] { - var info = userInfo - if sortKeysDuringEncoding { - info[CodingOption.sortKeys.infoKey] = true + public var sortKeysDuringEncoding: Bool { + get { + userInfo[BinaryEncoder.userInfoSortKey] as? Bool ?? false } - if prependNilIndexSetForUnkeyedContainers { - info[CodingOption.prependNilIndicesForUnkeyedContainers.infoKey] = true + set { + userInfo[BinaryEncoder.userInfoSortKey] = newValue } - return info } - + + /// Any contextual information set by the user for encoding. + public var userInfo: [CodingUserInfoKey : Any] = [:] + /** - Create a new binary encoder. + Create a new encoder. - Note: An encoder can be used to encode multiple messages. */ public init() { - + } - + /** Encode a value to binary data. - Parameter value: The value to encode - Returns: The encoded data - Throws: Errors of type `EncodingError` */ - public func encode(_ value: Encodable) throws -> Data { - let isOptional = value is AnyOptional - let root = EncodingNode(path: [], info: fullInfo, optional: isOptional) - try value.encode(to: root) - return root.data + public func encode(_ value: T) throws -> Data where T: Encodable { + // Directly encode primitives, otherwise: + // - Data would be encoded as an unkeyed container + // - There would always be a nil indicator byte 0x00 at the beginning + // NOTE: The comparison of the types is necessary, since otherwise optionals are matched as well. + if T.self is EncodablePrimitive.Type, let value = value as? EncodablePrimitive { + return value.encodedData + } + let encoder = EncodingNode(needsLengthData: false, codingPath: [], userInfo: userInfo) + try value.encode(to: encoder) + return try encoder.completeData() + } + + /** + Encode a single value to binary data using a default encoder. + - Parameter value: The value to encode + - Returns: The encoded data + - Throws: Errors of type `EncodingError` + */ + public static func encode(_ value: Encodable) throws -> Data { + try BinaryEncoder().encode(value) } + // MARK: Stream encoding + /** Encodes a value to binary data for use in a data stream. - This function differs from 'normal' encoding since additional length information is embedded into the binary data. + This function differs from 'normal' encoding by the additional length information prepended to the element.. This information is used when decoding values from a data stream. - Note: This function is not exposed publicly to keep the API easy to understand. @@ -115,19 +102,8 @@ public final class BinaryEncoder { - Throws: Errors of type `EncodingError` */ func encodeForStream(_ value: Encodable) throws -> Data { - let isOptional = value is AnyOptional - let root = EncodingNode(path: [], info: fullInfo, optional: isOptional) - try value.encode(to: root) - return root.dataWithLengthInformationIfRequired - } - - /** - Encode a single value to binary data using a default encoder. - - Parameter value: The value to encode - - Returns: The encoded data - - Throws: Errors of type `EncodingError` - */ - public static func encode(_ value: Encodable) throws -> Data { - try BinaryEncoder().encode(value) + let encoder = EncodingNode(needsLengthData: true, codingPath: [], userInfo: userInfo) + try value.encode(to: encoder) + return try encoder.completeData() } } diff --git a/Sources/BinaryCodable/BinaryFileDecoder.swift b/Sources/BinaryCodable/BinaryFileDecoder.swift index b900ac0..24f6526 100644 --- a/Sources/BinaryCodable/BinaryFileDecoder.swift +++ b/Sources/BinaryCodable/BinaryFileDecoder.swift @@ -31,11 +31,11 @@ import Foundation */ public final class BinaryFileDecoder where Element: Decodable { - private let handle: FileHandle + private let file: FileHandle private let decoder: BinaryDecoder - private let buffer: BinaryStreamBuffer + private let endIndex: UInt64 /** Create a file decoder. @@ -47,10 +47,16 @@ public final class BinaryFileDecoder where Element: Decodable { - Throws: An error, if the file handle could not be created. */ public init(fileAt url: URL, decoder: BinaryDecoder = .init()) throws { - let handle = try FileHandle(forReadingFrom: url) - self.handle = handle + let file = try FileHandle(forReadingFrom: url) + self.file = file self.decoder = decoder - self.buffer = BinaryStreamBuffer(data: handle) + if #available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) { + self.endIndex = try file.seekToEnd() + try file.seek(toOffset: 0) + } else { + self.endIndex = file.seekToEndOfFile() + file.seek(toFileOffset: 0) + } } deinit { @@ -66,9 +72,9 @@ public final class BinaryFileDecoder where Element: Decodable { */ public func close() throws { if #available(macOS 10.15, iOS 13.0, tvOS 13.4, watchOS 6.2, *) { - try handle.close() + try file.close() } else { - handle.closeFile() + file.closeFile() } } @@ -78,7 +84,7 @@ public final class BinaryFileDecoder where Element: Decodable { Read all elements in the file, and handle each element using a closure. - Parameter elementHandler: The closure to handle each element as it is decoded. - - Throws: Decoding errors of type ``DecodingError``. + - Throws: Decoding errors of type `DecodingError`. */ public func read(_ elementHandler: (Element) throws -> Void) throws { while let element = try readElement() { @@ -89,7 +95,7 @@ public final class BinaryFileDecoder where Element: Decodable { /** Read all elements at once. - Returns: The elements decoded from the file. - - Throws: Errors of type ``DecodingError`` + - Throws: Errors of type `DecodingError` */ public func readAll() throws -> [Element] { var result = [Element]() @@ -117,77 +123,54 @@ public final class BinaryFileDecoder where Element: Decodable { /** Read a single elements from the current position in the file. - Returns: The element decoded from the file, or `nil`, if no more data is available. - - Throws: Errors of type ``DecodingError`` + - Throws: Errors of type `DecodingError` */ public func readElement() throws -> Element? { - guard buffer.hasMoreBytes else { + guard !isAtEnd else { return nil } - do { - let element = try decodeNextElement() - // Remove the buffered data since element was correctly decoded - buffer.discardUsedBufferData() - return element - } catch { - buffer.discardUsedBufferData() - throw error - } + // Read length/nil indicator + let data = try decodeNextDataOrNilElement() + let node = try DecodingNode(data: data, parentDecodedNil: true, codingPath: [], userInfo: decoder.userInfo) + return try Element.init(from: node) } +} - private func decodeNextElement() throws -> Element { - if Element.self is AnyOptional.Type { - return try decodeOptional() - } else { - return try decodeNonOptional() +extension BinaryFileDecoder: DecodingDataProvider { + + var codingPath: [any CodingKey] { [] } + + private var currentOffset: UInt64 { + if #available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) { + return (try? file.offset()) ?? endIndex } + return file.offsetInFile } - private func decodeNonOptional() throws -> Element { - return try decoder.decode(fromStream: buffer) + var isAtEnd: Bool { + currentOffset >= endIndex } - private func decodeOptional() throws -> Element { - guard try buffer.getByte(path: []) > 0 else { - return try decoder.decode(from: Data()) - } - return try decodeNonOptional() + func nextByte() throws -> UInt64 { + let byte = try getBytes(1).first! + return UInt64(byte) } -} -extension FileHandle: BinaryStreamProvider { + var numberOfRemainingBytes: Int { + Int(endIndex - currentOffset) + } - func getBytes(_ count: Int, path: [CodingKey]) throws -> Data { + func getBytes(_ count: Int) throws -> Data { guard #available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) else { - let data = readData(ofLength: count) + let data = file.readData(ofLength: count) guard data.count == count else { - throw DecodingError.prematureEndOfData(path) + throw DecodingError.prematureEndOfData([]) } return data } - guard let data = try read(upToCount: count) else { - throw DecodingError.prematureEndOfData(path) + guard let data = try file.read(upToCount: count) else { + throw DecodingError.prematureEndOfData([]) } return data } - - var hasMoreBytes: Bool { - if #available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) { - return moreBytesAvailable - } - - if readData(ofLength: 1).first != nil { - seek(toFileOffset: offsetInFile - 1) - return true - } - return false - } - - @available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) - private var moreBytesAvailable: Bool { - if (try? read(upToCount: 1)?.first) != nil { - try? seek(toOffset: offset() - 1) - return true - } - return false - } } diff --git a/Sources/BinaryCodable/BinaryFileEncoder.swift b/Sources/BinaryCodable/BinaryFileEncoder.swift index ee8b395..f9a3cc4 100644 --- a/Sources/BinaryCodable/BinaryFileEncoder.swift +++ b/Sources/BinaryCodable/BinaryFileEncoder.swift @@ -56,7 +56,7 @@ public final class BinaryFileEncoder where Element: Encodable { Write a single element to the file. - Note: This function will throw an error or exception if the file handle has already been closed. - Parameter element: The element to encode. - - Throws: Errors of type ``EncodingError`` + - Throws: Errors of type `EncodingError` */ public func write(_ element: Element) throws { let data = try stream.encode(element) @@ -71,9 +71,9 @@ public final class BinaryFileEncoder where Element: Encodable { Write a sequence of elements to the file. This is a convenience function calling `write(_ element:)` for each element of the sequence in order. - + - Parameter sequence: The sequence to encode - - Throws: Errors of type ``EncodingError`` + - Throws: Errors of type `EncodingError` */ public func write(contentsOf sequence: S) throws where S: Sequence, S.Element == Element { try sequence.forEach(write) diff --git a/Sources/BinaryCodable/BinaryStreamDecoder.swift b/Sources/BinaryCodable/BinaryStreamDecoder.swift index 71ad31e..e60c406 100644 --- a/Sources/BinaryCodable/BinaryStreamDecoder.swift +++ b/Sources/BinaryCodable/BinaryStreamDecoder.swift @@ -17,7 +17,23 @@ public final class BinaryStreamDecoder where Element: Decodable { */ private let decoder: BinaryDecoder - private let buffer: DataDecodingBuffer + /** + The buffer storing the data while elements are being decoded. + */ + private var buffer = Data() + + /** + The length of the next element. + + This property is `nil`, while the length is being decoded. + */ + private var lengthOfCurrentElement: Int? + + /// The current length of the next element, while decoding the bytes + private var currentLength: UInt64 = 0 + + /// The number of length bytes already decoded + private var numberOfLengthBytes = 0 /** Create a stream decoder. @@ -26,26 +42,40 @@ public final class BinaryStreamDecoder where Element: Decodable { */ public init(decoder: BinaryDecoder = .init()) { self.decoder = decoder - self.buffer = DataDecodingBuffer() } - - + + // MARK: Buffer operations + + private var isAtEnd: Bool { + buffer.isEmpty + } + + private func nextByte() -> UInt8? { + buffer.popFirst() + } + + private func nextBytes(_ count: Int) -> Data { + let data = buffer[buffer.startIndex.. where Element: Decodable { */ @discardableResult public func discardBytes(_ count: Int) -> Int { - buffer.discard(bytes: count) + let oldCount = buffer.count + buffer = buffer.dropFirst(count) + return oldCount - buffer.count } // MARK: Decoding - /** - Read elements from the stream until no more bytes are available. - - - Parameter returnElementsBeforeError: If set to `true`, - then all successfully decoded elements will be returned if an error occurs. Defaults to `false` - - Throws: Decoding errors of type ``DecodingError``. - - Note: This function is deprecated. If you want to decode elements, use ``decodeElements()``, or use ``decodeElementsUntilError()``, if you expect errors in the data stream. - */ - @available(*, deprecated, message: "Use add() and decodeElementsUntilError() or decodeElements() instead.") - public func decode(_ data: Data, returnElementsBeforeError: Bool = false) throws -> [Element] { - buffer.addToBuffer(data) - - var results = [Element]() - while buffer.hasMoreBytes { - do { - guard let element = try decodeElement() else { - return results - } - results.append(element) - } catch { - if returnElementsBeforeError { - return results - } - throw error - } - } - return results - } - /** Read elements from the stream until an error occurs. - + This function may be useful if the data is corrupted. The readable elements can be decoded until a decoding error is encountered. - Returns: The elements that could be decoded until an error occured or the buffer was insufficient, and the decoding error, if one occured. */ public func decodeElementsUntilError() -> (elements: [Element], error: Error?) { var results = [Element]() - while buffer.hasMoreBytes { + while !buffer.isEmpty { do { guard let element = try decodeElement() else { return (results, nil) @@ -108,18 +111,18 @@ public final class BinaryStreamDecoder where Element: Decodable { } return (results, nil) } - + /** Read elements until no more data is available. - + - Note: If a decoding error occurs within the data, then the elements successfully decoded before are lost. If you expect decoding errors to occur, either decode elements individually using ``decodeElement()``, or use ``decodeElementsUntilError()``. - Returns: The elements that could be decoded with the available data. - - Throws: Decoding errors of type ``DecodingError``. + - Throws: Decoding errors of type `DecodingError`. */ public func decodeElements() throws -> [Element] { var results = [Element]() - while buffer.hasMoreBytes { + while !isAtEnd { guard let element = try decodeElement() else { return results } @@ -130,27 +133,83 @@ public final class BinaryStreamDecoder where Element: Decodable { /** Attempt to decode a single element from the stream. - + If insufficient bytes are available, then no element is returned, and the internal buffer is kept intact. - Returns: The decoded element, if enough bytes are available. - - Throws: Decoding errors of type ``DecodingError``. + - Throws: Decoding errors of type `DecodingError`. */ public func decodeElement() throws -> Element? { - do { - let element: Element = try decoder.decode(fromStream: buffer) - // Remove the buffered data since element was correctly decoded - buffer.discardUsedBufferData() - return element - } catch { - if buffer.didExceedBuffer { - // Insufficient data for now, reuse buffered bytes next time - buffer.resetToBeginOfBuffer() - return nil - } else { - // Remove the buffered data since element was correctly decoded - buffer.discardUsedBufferData() - throw error + if let length = lengthOfCurrentElement { + return try decodeElement(length: length) + } + + // Start by checking the nil bit and decode the first byte of the length + if numberOfLengthBytes == 0 { + guard let first = nextByte() else { + return nil // Wait for first byte + } + + // Check the nil indicator bit + guard first & 0x01 == 0 else { + // Decode a nil element (throws an error if the element can't be nil) + let node = try DecodingNode(data: nil, parentDecodedNil: true, codingPath: [], userInfo: decoder.userInfo) + return try Element.init(from: node) + // No need to reset any state + } + + // Ensure that the decoding of the first byte is skipped until a full element is decoded + numberOfLengthBytes = 1 + + // Check if more length bytes are needed + if first & 0x80 == 0 { + // Finished with the length + return try decodeElement(length: Int(first >> 1)) } + // Start the length decoding + currentLength = UInt64(first) + } + + // Continue decoding the length + while numberOfLengthBytes < 8 { + guard let nextByte = nextByte() else { + return nil // Wait for more bytes + } + // Insert the last 7 bit of the byte at the end + currentLength += UInt64(nextByte & 0x7F) << (numberOfLengthBytes * 7) + numberOfLengthBytes += 1 + // Check if an additional byte is coming + guard nextByte & 0x80 > 0 else { + // Finished decoding the length + return try decodeElement(length: Int(currentLength >> 1)) + } + } + // Decode the last byte + guard let nextByte = nextByte() else { + return nil // Wait for more bytes } + // The 9th byte has no next-byte bit, so all 8 bits are used + currentLength += UInt64(nextByte) << 56 + return try decodeElement(length: Int(currentLength >> 1)) + } + + private func decodeElement(length: Int) throws -> Element? { + guard numberOfBytesInBuffer >= length else { + // Wait for enough bytes to decode the next element + self.lengthOfCurrentElement = length + return nil + } + let data = nextBytes(length) + + // Reset the state to begin with length/nil decoding + self.lengthOfCurrentElement = nil + self.currentLength = 0 + self.numberOfLengthBytes = 0 + + let node = try DecodingNode(data: data, parentDecodedNil: true, codingPath: [], userInfo: decoder.userInfo) + return try Element.init(from: node) + } + + private func corrupted(_ message: String) -> DecodingError { + return .corrupted(message, codingPath: []) } } diff --git a/Sources/BinaryCodable/BinaryStreamEncoder.swift b/Sources/BinaryCodable/BinaryStreamEncoder.swift index 55f8c1f..c588969 100644 --- a/Sources/BinaryCodable/BinaryStreamEncoder.swift +++ b/Sources/BinaryCodable/BinaryStreamEncoder.swift @@ -62,7 +62,7 @@ public final class BinaryStreamEncoder where Element: Encodable { Decoding using a simple ``BinaryDecoder`` will not be successful. - Parameter element: The element to encode. - Returns: The next chunk of the encoded binary stream. - - Throws: Errors of type ``EncodingError`` + - Throws: Errors of type `EncodingError` */ public func encode(_ element: Element) throws -> Data { try encoder.encodeForStream(element) @@ -74,7 +74,7 @@ public final class BinaryStreamEncoder where Element: Encodable { This function performs multiple calls to ``encode(_:)`` to convert all elements of the sequence, and then returns the joined data. - Parameter sequence: The sequence of elements to encode - Returns: The binary data of the encoded sequence elements - - Throws: Errors of type ``EncodingError`` + - Throws: Errors of type `EncodingError` */ public func encode(contentsOf sequence: S) throws -> Data where S: Sequence, S.Element == Element { try sequence.map(encode).joinedData diff --git a/Sources/BinaryCodable/BinaryStreamProvider.swift b/Sources/BinaryCodable/BinaryStreamProvider.swift deleted file mode 100644 index 689cebc..0000000 --- a/Sources/BinaryCodable/BinaryStreamProvider.swift +++ /dev/null @@ -1,91 +0,0 @@ -import Foundation - -/** - A protocol to provide continuous data for decoding. - - This protocol can be used in conjuction with ``BinaryStreamDecoder`` to decode longer data streams of sequential elements. - This can be helpful when either the data is not immediatelly available all at once (e.g. when receiving data over a network) - or when the data is too large to keep it in memory (e.g. when reading a large file). - - Implement this protocol according to the data source for encoded data, and then pass it to a ``BinaryStreamDecoder`` to decode individual elements. - - Note: Successful decoding is only possible if the data stream is also encoded using a ``BinaryStreamEncoder``. - */ -protocol BinaryStreamProvider { - - /** - A callback to the data source to get the next chuck of data as required by the current decoding step. - - The data should be provided in a best-effort manner, if they are available. - If insufficient bytes are available, e.g. if network data is still in transit, - then a ``DecodingError`` of type `dataCorrupted` should be thrown. - This signals to the decoder that not all data is available, - so that decoding can continue once more data is available. - - Note: There is no need to buffer incoming data until an element is successfully decoded. - Any bytes passed to the decoder as the result of this function are internally buffered and can be discarded. - - - Parameter count: The number of bytes to provide as the result. - - Returns: The next `count` bytes in the data stream. - */ - func getBytes(_ count: Int, path: [CodingKey]) throws -> Data - - /** - Indicate if there are any bytes to read for the decoder. - - If the data stream has at least one byte available, then this function should return `true`. - The decoder uses this function to check if an attempt should be made to decode more elements. - - - Note: There is no need to buffer incoming data until an element is successfully decoded. - It is safe to return `true`, even if not enough bytes are available to decode a full element. - - - Returns: `true`, if bytes are available for decoding. - */ - var hasMoreBytes: Bool { get } -} - -extension BinaryStreamProvider { - - func getByte(path: [CodingKey]) throws -> UInt8 { - let data = try getBytes(1, path: path) - return data[data.startIndex] - } - - func getDataOfVarint(path: [CodingKey]) throws -> Data { - var result = [UInt8]() - for _ in 0...7 { - let byte = try getByte(path: path) - result.append(byte) - if byte & 0x80 == 0 { - return Data(result) - } - } - let byte = try getByte(path: path) - result.append(byte) - return Data(result) - } - - func getVarint(path: [CodingKey]) throws -> Int { - let data = try getDataOfVarint(path: path) - return try .init(fromVarint: data, path: path) - } - - func getData(for dataType: DataType, path: [CodingKey]) throws -> Data { - switch dataType { - case .variableLengthInteger: - return try getDataOfVarint(path: path) - case .byte: - return try getBytes(1, path: path) - case .twoBytes: - return try getBytes(2, path: path) - case .variableLength: - let count = try getVarint(path: path) - guard count >= 0 else { - throw DecodingError.invalidDataSize(path) - } - return try getBytes(count, path: path) - case .fourBytes: - return try getBytes(4, path: path) - case .eightBytes: - return try getBytes(8, path: path) - } - } -} diff --git a/Sources/BinaryCodable/Common/AbstractNode.swift b/Sources/BinaryCodable/Common/AbstractNode.swift index ea88c43..98883f9 100644 --- a/Sources/BinaryCodable/Common/AbstractNode.swift +++ b/Sources/BinaryCodable/Common/AbstractNode.swift @@ -25,33 +25,14 @@ class AbstractNode { */ let userInfo: UserInfo - - /** - Add a set of indices for `nil` values in unkeyed containers. - - This option changes the encoding of unkeyed sequences like arrays with optional values. - - If this option is set to `true`, then the encoded binary data first contains a list of indexes for each position where `nil` is encoded. - After this data the remaining (non-nil) values are added. - If this option is `false`, then each value is prepended with a byte `1` for non-nil values, and a byte `0` for `nil` values. - - Using an index set is generally more efficient, expect for large sequences with many `nil` values. - An index set is encoded using first the number of elements, and then each element, all encoded as var-ints. - - - Note: This option defaults to `true` - - Note: To decode successfully, the decoder must use the same setting for `containsNilIndexSetForUnkeyedContainers`. - */ - var prependNilIndexSetForUnkeyedContainers: Bool { - userInfo.has(.prependNilIndicesForUnkeyedContainers) - } - /** Create an abstract node. - - Parameter path: The path to get to this point in encoding or decoding - - Parameter info: Contextual information set by the user + - Parameter codingPath: The path to get to this point in encoding or decoding + - Parameter userInfo: Contextual information set by the user */ - init(path: [CodingKey], info: UserInfo) { - self.codingPath = path - self.userInfo = info + init(codingPath: [CodingKey], userInfo: UserInfo) { + self.codingPath = codingPath + self.userInfo = userInfo } + } diff --git a/Sources/BinaryCodable/Common/AnyCodingKey.swift b/Sources/BinaryCodable/Common/AnyCodingKey.swift deleted file mode 100644 index 7912907..0000000 --- a/Sources/BinaryCodable/Common/AnyCodingKey.swift +++ /dev/null @@ -1,35 +0,0 @@ - -/// Helpful for appending mixed coding keys. -struct AnyCodingKey: CodingKey { - var stringValue: String - var intValue: Int? - - init(intValue: Int) { - self.intValue = intValue - self.stringValue = "\(intValue)" - } - - init(stringValue: String) { - self.intValue = nil - self.stringValue = stringValue - } -} - -extension AnyCodingKey { - init(_ key: T) { - self.stringValue = key.stringValue - self.intValue = key.intValue - } -} - -extension AnyCodingKey: ExpressibleByStringLiteral { - init(stringLiteral value: String) { - self.init(stringValue: value) - } -} - -extension AnyCodingKey: ExpressibleByIntegerLiteral { - init(integerLiteral value: Int) { - self.init(intValue: value) - } -} diff --git a/Sources/BinaryCodable/Common/DataType.swift b/Sources/BinaryCodable/Common/DataType.swift deleted file mode 100644 index e08aaeb..0000000 --- a/Sources/BinaryCodable/Common/DataType.swift +++ /dev/null @@ -1,96 +0,0 @@ -import Foundation - -/** - The data type specifying how a value is encoded on the wire. - - The data type is mixed into the key for each value to indicate how many bytes the value occupies in the following bytes. - - The data type is equivalent to the [Protocol Buffer Wire Type](https://developers.google.com/protocol-buffers/docs/encoding#structure), but extended to support more data types. - */ -public enum DataType: Int { - - /** - An integer value encoded as a Base128 Varint. - The length can be determined by reading the value, - where the first (MSB) indicates whether another byte follows. - A Varint consumes up to 9 bytes, where all bits from the last byte are used, - resulting in `8 * 7 + 8 = 64` usable bits. - - For Protobuf encoding, the Varint can use up to 10 bytes, since the MSB is used for all bytes, including byte 9. - - See also [Protocol Buffers: Base 128 Varints](https://developers.google.com/protocol-buffers/docs/encoding#varints). - - Used for: `Int`, `Int32`, `Int64`, `UInt`, `UInt32`, `UInt64`, `Bool` - */ - case variableLengthInteger = 0 - - /** - The value is encoded as a single byte. - - - Note: This data type is incompatible with the protocol buffer specification. - - Used for: `UInt8`, `Int8` - */ - case byte = 6 - - /** - The value is encoded as two bytes. - - - Note: This data type is incompatible with the protocol buffer specification. - - Used for: `Int16`, `UInt16` - */ - case twoBytes = 7 - - /** - The value is encoded using first a length (as a UInt64 var-int) followed by the bytes. - - Used by: `String`, `Data`, complex types - */ - case variableLength = 2 - - /** - The value is encoded using four bytes. - - Used for: `Float`, `FixedWidth`, `FixedWidth` - */ - case fourBytes = 5 - - /** - The value is encoded using eight bytes. - - Used by: `Double`, `FixedWidth`, `FixedWidth`, `FixedWidth`, `FixedWidth` - */ - case eightBytes = 1 - - /** - Decode a data type from an integer tag. - - The integer tag includes both the integer field key (or string key length) and the data type, - where the data type is encoded in the three LSB. - - Parameter value: The raw tag value. - - Throws: `DecodingError.dataCorrupted()`, if the data type is unknown (3 or 4) - */ - init(decodeFrom value: Int, path: [CodingKey]) throws { - let rawDataType = value & 0x7 - guard let dataType = DataType(rawValue: rawDataType) else { - let context = DecodingError.Context(codingPath: path, debugDescription: "Unknown data type \(rawDataType)") - throw DecodingError.dataCorrupted(context) - } - self = dataType - } - - /** - Indicate that the datatype is also available in the protobuf specification. - - All data types except `.byte` and `.twoBytes` are compatible. - */ - var isProtobufCompatible: Bool { - switch self { - case .variableLengthInteger, .variableLength, .fourBytes, .eightBytes: - return true - case .byte, .twoBytes: - return false - } - } -} diff --git a/Sources/BinaryCodable/Common/SuperEncoderKey.swift b/Sources/BinaryCodable/Common/SuperCodingKey.swift similarity index 95% rename from Sources/BinaryCodable/Common/SuperEncoderKey.swift rename to Sources/BinaryCodable/Common/SuperCodingKey.swift index 1194533..d19dc77 100644 --- a/Sources/BinaryCodable/Common/SuperEncoderKey.swift +++ b/Sources/BinaryCodable/Common/SuperCodingKey.swift @@ -5,7 +5,7 @@ import Foundation The key uses either the string key `super`, or the integer key `0`. */ -struct SuperEncoderKey: CodingKey { +struct SuperCodingKey: CodingKey { /** Create a new super encoding key. diff --git a/Sources/BinaryCodable/Decoding/AbstractDecodingNode.swift b/Sources/BinaryCodable/Decoding/AbstractDecodingNode.swift index d80cb1b..1ce3a81 100644 --- a/Sources/BinaryCodable/Decoding/AbstractDecodingNode.swift +++ b/Sources/BinaryCodable/Decoding/AbstractDecodingNode.swift @@ -1,6 +1,25 @@ import Foundation +/** + A class to provide decoding functions to all decoding containers. + */ class AbstractDecodingNode: AbstractNode { - + let parentDecodedNil: Bool + + init(parentDecodedNil: Bool, codingPath: [CodingKey], userInfo: UserInfo) { + self.parentDecodedNil = parentDecodedNil + super.init(codingPath: codingPath, userInfo: userInfo) + } + + func decode(element: Data?, type: T.Type, codingPath: [CodingKey]) throws -> T where T: Decodable { + if let BaseType = T.self as? DecodablePrimitive.Type { + guard let element else { + throw DecodingError.valueNotFound(type, codingPath: codingPath, "Found nil instead of expected type \(type)") + } + return try BaseType.init(data: element, codingPath: codingPath) as! T + } + let node = try DecodingNode(data: element, parentDecodedNil: parentDecodedNil, codingPath: codingPath, userInfo: userInfo) + return try type.init(from: node) + } } diff --git a/Sources/BinaryCodable/Decoding/BinaryStreamBuffer.swift b/Sources/BinaryCodable/Decoding/BinaryStreamBuffer.swift deleted file mode 100644 index 39531ba..0000000 --- a/Sources/BinaryCodable/Decoding/BinaryStreamBuffer.swift +++ /dev/null @@ -1,64 +0,0 @@ -import Foundation - -final class BinaryStreamBuffer { - - private let data: BinaryStreamProvider - - private var buffer: Data - - private var index: Data.Index - - init(data: BinaryStreamProvider) { - self.data = data - self.buffer = Data() - self.index = 0 - } - - private func getBufferedBytes(_ count: Int) -> Data { - let newIndex = index + count - guard newIndex <= buffer.endIndex else { - return buffer[index.. Data { - guard index <= buffer.endIndex else { - return Data() - } - return buffer[index.. Data { - let bufferedBytes = getBufferedBytes(count) - let remaining = count - bufferedBytes.count - guard remaining > 0 else { - index += count - return bufferedBytes - } - let remainingBytes = try data.getBytes(remaining, path: path) - addToBuffer(remainingBytes) - return bufferedBytes + remainingBytes - } - - var hasMoreBytes: Bool { - index < buffer.endIndex || data.hasMoreBytes - } -} diff --git a/Sources/BinaryCodable/Decoding/DataDecoder.swift b/Sources/BinaryCodable/Decoding/DataDecoder.swift deleted file mode 100644 index adc540f..0000000 --- a/Sources/BinaryCodable/Decoding/DataDecoder.swift +++ /dev/null @@ -1,30 +0,0 @@ -import Foundation - -final class DataDecoder: BinaryStreamProvider { - - let data: Data - - var index: Data.Index - - init(data: Data) { - self.data = data - self.index = data.startIndex - } - - var hasMoreBytes: Bool { - index < data.endIndex - } - - func getAllData() -> Data { - data - } - - func getBytes(_ count: Int, path: [CodingKey]) throws -> Data { - let newIndex = index + count - guard newIndex <= data.endIndex else { - throw DecodingError.prematureEndOfData(path) - } - defer { index = newIndex } - return data[index.. Int { - let bytesToRemove = min(buffer.count, bytes) - buffer = buffer.dropFirst(bytesToRemove) - index = buffer.startIndex - return bytesToRemove - } - - var totalBufferSize: Int { - buffer.count - } - - var unusedBytes: Int { - buffer.endIndex - index - } -} - -// MARK: BinaryStreamProvider - -extension DataDecodingBuffer: BinaryStreamProvider { - - func getBytes(_ count: Int, path: [CodingKey]) throws -> Data { - let newIndex = index + count - guard newIndex <= buffer.endIndex else { - didExceedBuffer = true - throw DecodingError.prematureEndOfData(path) - } - defer { index = newIndex } - return buffer[index.. UInt64 + + var numberOfRemainingBytes: Int { get } + + func getBytes(_ count: Int) throws -> Data +} + +extension DecodingDataProvider { + + func remainingBytes() throws -> Data { + try getBytes(numberOfRemainingBytes) + } + + /** + Decode an unsigned integer using variable-length encoding starting at a position. + */ + func decodeUInt64() throws -> UInt64 { + let start = try nextByte() + return try decodeUInt64(startByte: start) + } + + /** + Decode an unsigned integer using variable-length encoding starting at a position. + */ + private func decodeUInt64(startByte: UInt64) throws -> UInt64 { + guard startByte & 0x80 > 0 else { + return startByte + } + + var result = startByte & 0x7F + // There are always 7 usable bits per byte, for 8 bytes + for byteIndex in 1..<8 { + let nextByte = try nextByte() + // Insert the last 7 bit of the byte at the end + result += UInt64(nextByte & 0x7F) << (byteIndex*7) + // Check if an additional byte is coming + guard nextByte & 0x80 > 0 else { + return result + } + } + + // The 9th byte has no next-byte bit, so all 8 bits are used + let nextByte = try nextByte() + result += UInt64(nextByte) << 56 + return result + } + + private func decodeNilIndicatorOrLength() throws -> Int? { + let first = try nextByte() + + // Check the nil indicator bit + guard first & 0x01 == 0 else { + return nil + } + // The rest is the length, encoded as a varint + let rawLengthValue = try decodeUInt64(startByte: first) + + // Remove the nil indicator bit + return Int(rawLengthValue >> 1) + } + + func decodeNextDataOrNilElement() throws -> Data? { + guard let length = try decodeNilIndicatorOrLength() else { + return nil + } + return try getBytes(length) + } + + func decodeUnkeyedElements() throws -> [Data?] { + var elements = [Data?]() + while !isAtEnd { + let element = try decodeNextDataOrNilElement() + elements.append(element) + } + return elements + } + + func decodeNextKey() throws -> DecodingKey { + // First, decode the next key + let rawKeyOrLength = try decodeUInt64() + let lengthOrKey = Int(rawKeyOrLength >> 1) + + guard rawKeyOrLength & 1 == 1 else { + // Int key + return .integer(lengthOrKey) + } + + // String key, decode length bytes + let stringData = try getBytes(lengthOrKey) + let stringKey = try String(data: stringData, codingPath: codingPath) + return .string(stringKey) + } + + func decodeKeyDataPairs() throws -> [DecodingKey : Data?] { + var elements = [DecodingKey : Data?]() + while !isAtEnd { + let key = try decodeNextKey() + guard !isAtEnd else { + throw corrupted("Unexpected end of data after decoding key") + } + let element = try decodeNextDataOrNilElement() + guard elements[key] == nil else { + throw corrupted("Found multiple values for key \(key)") + } + elements[key] = element + } + return elements + } + + + /** + Decode just the nil indicator byte, but don't extract a length. Uses all remaining bytes for the value. + - Note: This function is only used for the root node + */ + func decodeSingleElementWithNilIndicator() throws -> Data? { + let first = try nextByte() + // Check the nil indicator bit + switch first { + case 0: + return try remainingBytes() + case 1: + guard isAtEnd else { + throw corrupted("\(numberOfRemainingBytes) additional bytes found after nil indicator") + } + return nil + default: + throw corrupted("Found unexpected nil indicator \(first)") + } + } + + /** + Check if the value is `nil` or return the wrapped data. + - Note: This function is the equivalent to `encodedDataWithNilIndicatorAndLength()`. + */ + func decodingSingleElementWithNilIndicatorAndLength() throws -> Data? { + let first = try nextByte() + // Check the nil indicator bit + guard first & 0x01 == 0 else { + return nil + } + // The rest is the length, encoded as a varint + let rawLengthValue = try decodeUInt64() + // Remove the nil indicator bit + let length = Int(rawLengthValue >> 1) + let wrappedElement = try getBytes(length) + guard isAtEnd else { + throw corrupted("Found \(numberOfRemainingBytes) unexpected bytes after encoded data") + } + return wrappedElement + } + + func corrupted(_ message: String) -> DecodingError { + return .corrupted(message, codingPath: codingPath) + } +} diff --git a/Sources/BinaryCodable/Decoding/DecodingKey.swift b/Sources/BinaryCodable/Decoding/DecodingKey.swift index 9889cb0..773039a 100644 --- a/Sources/BinaryCodable/Decoding/DecodingKey.swift +++ b/Sources/BinaryCodable/Decoding/DecodingKey.swift @@ -1,68 +1,50 @@ import Foundation -enum DecodingKey { - case intKey(Int) - case stringKey(String) +/** + A decoded key + */ +enum DecodingKey: Hashable { - func isEqual(to key: CodingKey) -> Bool { - switch self { - case .intKey(let value): - return value == key.intValue - case .stringKey(let value): - return value == key.stringValue - } - } + /// A decoded integer key + case integer(Int) - private static func isStringKey(_ value: Int) -> Bool { - value & 0x08 > 0 - } + /// A decoded string key + case string(String) - private static func decodeKey(_ raw: Int, from decoder: BinaryStreamProvider, path: [CodingKey]) throws -> DecodingKey { - let value = raw >> 4 - guard isStringKey(raw) else { - return DecodingKey.intKey(value) - } - guard value >= 0 else { - throw DecodingError.invalidDataSize(path) + func asKey(_ type: T.Type = T.self) -> T? where T: CodingKey { + switch self { + case .integer(let int): + return .init(intValue: int) + case .string(let string): + return .init(stringValue: string) } - let stringKeyData = try decoder.getBytes(value, path: path) - let stringKey = try String(decodeFrom: stringKeyData, path: path) - return DecodingKey.stringKey(stringKey) } +} - static func decode(from decoder: BinaryStreamProvider, path: [CodingKey]) throws -> (key: DecodingKey, dataType: DataType) { - let raw = try decoder.getVarint(path: path) - let dataType = try DataType(decodeFrom: raw, path: path) - let key = try decodeKey(raw, from: decoder, path: path) - return (key, dataType) - } +extension DecodingKey { - static func decodeProto(from decoder: BinaryStreamProvider, path: [CodingKey]) throws -> (key: DecodingKey, dataType: DataType) { - let raw = try decoder.getVarint(path: path) - let dataType = try DataType(decodeFrom: raw, path: path) - let fieldNumber = raw >> 3 - try IntKeyWrapper.checkFieldBounds(fieldNumber) - let key = DecodingKey.intKey(fieldNumber) - return (key, dataType) + /** + Create a decoding key from an abstract coding key + */ + init(key: CodingKey) { + if let intValue = key.intValue { + self = .integer(intValue) + } else { + self = .string(key.stringValue) + } } } -extension DecodingKey: Equatable { - -} - -extension DecodingKey: Hashable { +extension DecodingKey: ExpressibleByIntegerLiteral { + init(integerLiteral value: IntegerLiteralType) { + self = .integer(value) + } } -extension DecodingKey: CustomStringConvertible { +extension DecodingKey: ExpressibleByStringLiteral { - var description: String { - switch self { - case .intKey(let value): - return "\(value)" - case .stringKey(let value): - return value - } + init(stringLiteral value: StringLiteralType) { + self = .string(value) } } diff --git a/Sources/BinaryCodable/Decoding/DecodingNode.swift b/Sources/BinaryCodable/Decoding/DecodingNode.swift index 9b0d968..5b319b7 100644 --- a/Sources/BinaryCodable/Decoding/DecodingNode.swift +++ b/Sources/BinaryCodable/Decoding/DecodingNode.swift @@ -1,49 +1,59 @@ import Foundation +/** + A class acting as a decoder, to provide different containers for decoding. + */ final class DecodingNode: AbstractDecodingNode, Decoder { - private let storage: Storage + private let data: Data? - private let isOptional: Bool - - private let isInUnkeyedContainer: Bool + private var didCallContainer = false - init(storage: Storage, isOptional: Bool = false, path: [CodingKey], info: UserInfo, isInUnkeyedContainer: Bool = false) { - self.storage = storage - self.isOptional = isOptional - self.isInUnkeyedContainer = isInUnkeyedContainer - super.init(path: path, info: info) + init(data: Data?, parentDecodedNil: Bool, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) throws { + self.data = data + super.init(parentDecodedNil: parentDecodedNil, codingPath: codingPath, userInfo: userInfo) } - init(data: Data, isOptional: Bool = false, path: [CodingKey], info: UserInfo) { - self.storage = .data(data) - self.isOptional = isOptional - self.isInUnkeyedContainer = false - super.init(path: path, info: info) + private func registerContainer() throws { + guard !didCallContainer else { + throw DecodingError.corrupted("Multiple containers requested from decoder", codingPath: codingPath) + } + didCallContainer = true } - init(decoder: BinaryStreamProvider, isOptional: Bool = false, path: [CodingKey], info: UserInfo, isInUnkeyedContainer: Bool = false) { - self.storage = .decoder(decoder) - self.isOptional = isOptional - self.isInUnkeyedContainer = isInUnkeyedContainer - super.init(path: path, info: info) + private func getNonNilElement() throws -> Data { + try registerContainer() + // Non-root containers just use the data, which can't be nil + guard let data else { + throw DecodingError.corrupted("Container requested, but nil found", codingPath: codingPath) + } + return data + } + + private func getPotentialNilElement() throws -> Data? { + try registerContainer() + guard !parentDecodedNil else { + return data + } + guard let data else { + return nil + } + return try DecodingStorage(data: data, codingPath: codingPath) + .decodeSingleElementWithNilIndicator() } func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { - let container = try KeyedDecoder(data: storage.useAsData(path: codingPath), path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) + let data = try getNonNilElement() + return KeyedDecodingContainer(try KeyedDecoder(data: data, codingPath: codingPath, userInfo: userInfo)) } func unkeyedContainer() throws -> UnkeyedDecodingContainer { - return try UnkeyedDecoder(data: storage.useAsData(path: codingPath), path: codingPath, info: userInfo) + let data = try getNonNilElement() + return try UnkeyedDecoder(data: data, codingPath: codingPath, userInfo: userInfo) } func singleValueContainer() throws -> SingleValueDecodingContainer { - return ValueDecoder( - storage: storage, - isOptional: isOptional, - isInUnkeyedContainer: isInUnkeyedContainer, - path: codingPath, - info: userInfo) + let data = try getPotentialNilElement() + return ValueDecoder(data: data, codingPath: codingPath, userInfo: userInfo) } } diff --git a/Sources/BinaryCodable/Decoding/DecodingStorage.swift b/Sources/BinaryCodable/Decoding/DecodingStorage.swift index c33473b..3609358 100644 --- a/Sources/BinaryCodable/Decoding/DecodingStorage.swift +++ b/Sources/BinaryCodable/Decoding/DecodingStorage.swift @@ -1,24 +1,44 @@ import Foundation -enum Storage { - case data(Data) - case decoder(BinaryStreamProvider) - - func useAsData(path: [CodingKey]) throws -> Data { - switch self { - case .data(let data): - return data - case .decoder(let decoder): - return try decoder.getData(for: .variableLength, path: path) +final class DecodingStorage { + + let codingPath: [CodingKey] + + private let data: Data + + private var index: Data.Index + + init(data: Data, codingPath: [CodingKey]) { + self.codingPath = codingPath + self.data = data + self.index = data.startIndex + } +} + +extension DecodingStorage: DecodingDataProvider { + + var isAtEnd: Bool { + index >= data.endIndex + } + + func nextByte() throws -> UInt64 { + guard !isAtEnd else { + throw corrupted("Missing byte(s) decoding variable length integer") } + defer { index += 1 } + return UInt64(data[index]) + } + + var numberOfRemainingBytes: Int { + data.endIndex - index } - func useAsDecoder() -> BinaryStreamProvider { - switch self { - case .data(let data): - return DataDecoder(data: data) - case .decoder(let decoder): - return decoder + func getBytes(_ count: Int) throws -> Data { + let newIndex = index + count + guard newIndex <= data.endIndex else { + throw corrupted("Unexpected end of data") } + defer { index = newIndex } + return data[index..: AbstractDecodingNode, KeyedDecodingContainerProtocol where Key: CodingKey { - let content: [DecodingKey: Data] + let allKeys: [Key] - init(data: Data, path: [CodingKey], info: UserInfo) throws { - let decoder = DataDecoder(data: data) - var content = [DecodingKey: [Data]]() - while decoder.hasMoreBytes { - let (key, dataType) = try DecodingKey.decode(from: decoder, path: path) + /// - Note: The keys are not of type `Key`, since `CodingKey`s are not `Hashable`. + /// Also, some keys found in the data may not be convertable to `Key`, e.g. the `super` key, or obsoleted keys from older implementations. + private let elements: [DecodingKey: Data?] - do { - let data = try decoder.getData(for: dataType, path: path) - guard content[key] != nil else { - content[key] = [data] - continue - } - } catch DecodingError.dataCorrupted(let context) { - let codingKey = { - switch key { - case .stringKey(let stringValue): - return Key(stringValue: stringValue) - case .intKey(let intValue): - return Key(intValue: intValue) - } - }() - var newCodingPath = path - if let codingKey { - newCodingPath += [codingKey] - } - let newContext = DecodingError.Context( - codingPath: newCodingPath, - debugDescription: context.debugDescription, - underlyingError: context.underlyingError - ) - throw DecodingError.dataCorrupted(newContext) - } - - throw DecodingError.multipleValuesForKey(path, key) - } - self.content = content.mapValues { parts in - guard parts.count > 1 else { - return parts[0] - } - /// We only get here when `forceProtobufCompatibility = true` - /// So we need to prepend the length of each element - /// so that `KeyedEncoder` can decode it correctly - return parts.map { - $0.count.variableLengthEncoding + $0 - }.joinedData - } - super.init(path: path, info: info) + init(data: Data, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) throws { + self.elements = try DecodingStorage(data: data, codingPath: codingPath).decodeKeyDataPairs() + self.allKeys = elements.keys.compactMap { $0.asKey() } + super.init(parentDecodedNil: true, codingPath: codingPath, userInfo: userInfo) } - var allKeys: [Key] { - content.keys.compactMap { key in - switch key { - case .intKey(let value): - return Key(intValue: value) - case .stringKey(let value): - return Key(stringValue: value) - } + private func value(for intKey: Int?) -> Data?? { + guard let intKey else { + return nil } + return elements[.integer(intKey)] } - func contains(_ key: Key) -> Bool { - content.keys.contains { $0.isEqual(to: key) } + private func value(for stringKey: String) -> Data?? { + elements[.string(stringKey)] } - private func getData(forKey key: CodingKey) throws -> Data { - guard let data = content.first(where: { $0.key.isEqual(to: key) })?.value else { - let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Key not found") - throw DecodingError.keyNotFound(key, context) + private func value(for key: CodingKey) throws -> Data? { + let int = value(for: key.intValue) + let string = value(for: key.stringValue) + if int != nil && string != nil { + throw DecodingError.corrupted("Found value for int and string key", codingPath: codingPath + [key]) } - return data - } - - func decodeNil(forKey key: Key) throws -> Bool { - !contains(key) + guard let value = int ?? string else { + throw DecodingError.notFound(key, codingPath: codingPath + [key], "Missing value for key") + } + return value } - func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { - try wrapError(forKey: key) { - if let optionalType = type as? AnyOptional.Type { - if let data = try? getData(forKey: key) { - let node = DecodingNode(data: data, isOptional: true, path: codingPath, info: userInfo) - return try T.init(from: node) - } else { - return optionalType.nilValue as! T - } - } else if let Primitive = type as? DecodablePrimitive.Type { - let data = try getData(forKey: key) - return try Primitive.init(decodeFrom: data, path: codingPath + [key]) as! T - } else { - let data = try getData(forKey: key) - let node = DecodingNode(data: data, path: codingPath, info: userInfo) - return try T.init(from: node) - } - } + private func node(for key: CodingKey) throws -> DecodingNode { + let element = try value(for: key) + return try DecodingNode(data: element, parentDecodedNil: true, codingPath: codingPath + [key], userInfo: userInfo) } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - try wrapError(forKey: key) { - let data = try getData(forKey: key) - let container = try KeyedDecoder(data: data, path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) - } + KeyedDecodingContainer(try node(for: key).container(keyedBy: type)) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - try wrapError(forKey: key) { - let data = try getData(forKey: key) - return try UnkeyedDecoder(data: data, path: codingPath, info: userInfo) - } + try node(for: key).unkeyedContainer() } func superDecoder() throws -> Decoder { - let data = try getData(forKey: SuperEncoderKey()) - return DecodingNode(data: data, path: codingPath, info: userInfo) + try node(for: SuperCodingKey()) } func superDecoder(forKey key: Key) throws -> Decoder { - let data = try getData(forKey: key) - return DecodingNode(data: data, path: codingPath, info: userInfo) + try node(for: key) } - private func wrapError(forKey key: Key, _ block: () throws -> T) throws -> T { - do { - return try block() - } catch DecodingError.dataCorrupted(let context) { - let newContext = DecodingError.Context( - codingPath: codingPath + [key] + context.codingPath, - debugDescription: context.debugDescription, - underlyingError: context.underlyingError - ) - throw DecodingError.dataCorrupted(newContext) + func contains(_ key: Key) -> Bool { + if let intValue = key.intValue, elements[.integer(intValue)] != .none { + return true } + return elements[.string(key.stringValue)] != .none + } + + /** + Decodes a null value for the given key. + - Parameter key: The key that the decoded value is associated with. + - Returns: Whether the encountered key is contained + */ + func decodeNil(forKey key: Key) -> Bool { + /** + **Important note**: The implementation of `encodeNil(forKey:)` and `decodeNil(forKey:)` are implemented differently than the `Codable` documentation specifies: + - Throws: `DecodingError.keyNotFound` if `self` does not have an entry for the given key. + + If a value is `nil`, then it is not encoded. + We could change this by explicitly assigning a `nil` value during encoding. + But it would cause other problems, either breaking the decoding of double optionals (e.g. Int??), + or requiring an additional `nil` indicator for **all** values in keyed containers, + which would make the format less efficient. + + The alternative would be: + ``` + try value(for: key) == nil + ``` + */ + !contains(key) + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { + let element = try value(for: key) + return try decode(element: element, type: type, codingPath: codingPath + [key]) } } diff --git a/Sources/BinaryCodable/Decoding/Protobuf/Dictionary/ProtoDictDecodingNode.swift b/Sources/BinaryCodable/Decoding/Protobuf/Dictionary/ProtoDictDecodingNode.swift deleted file mode 100644 index 74dbf40..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/Dictionary/ProtoDictDecodingNode.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -final class ProtoDictDecodingNode: ProtoDecodingNode { - - override func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { - let container = try ProtoDictKeyedDecoder(data: storage.useAsData(path: codingPath), path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) - } - - override func unkeyedContainer() throws -> UnkeyedDecodingContainer { - try ProtoDictUnkeyedDecoder(data: storage.useAsData(path: codingPath), path: codingPath, info: userInfo) - } -} diff --git a/Sources/BinaryCodable/Decoding/Protobuf/Dictionary/ProtoDictKeyedDecoder.swift b/Sources/BinaryCodable/Decoding/Protobuf/Dictionary/ProtoDictKeyedDecoder.swift deleted file mode 100644 index 20da9a1..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/Dictionary/ProtoDictKeyedDecoder.swift +++ /dev/null @@ -1,51 +0,0 @@ -import Foundation - -final class ProtoDictKeyedDecoder: ProtoKeyedDecoder where Key: CodingKey { - - override init(data: Data, path: [CodingKey], info: UserInfo) throws { - let decoder = DataDecoder(data: data) - var content = [DecodingKey: [Data]]() - while decoder.hasMoreBytes { - let pairData = try decoder.getData(for: .variableLength, path: path) - let pairDecoder = DataDecoder(data: pairData) - let (keyField, keyDataType) = try DecodingKey.decodeProto(from: pairDecoder, path: path) - guard case .intKey(1) = keyField else { - throw ProtobufDecodingError.unexpectedDictionaryKey - } - let keyData = try pairDecoder.getData(for: keyDataType, path: path) - let key: DecodingKey - switch keyDataType { - case .variableLengthInteger: - let value = try Int(decodeFrom: keyData, path: path) - key = .intKey(value) - case .variableLength: - let value = try String(decodeFrom: keyData, path: path) - key = .stringKey(value) - default: - throw ProtobufDecodingError.unexpectedDictionaryKey - } - - let (valueField, valueDataType) = try DecodingKey.decodeProto(from: pairDecoder, path: path) - guard case .intKey(2) = valueField else { - throw ProtobufDecodingError.unexpectedDictionaryKey - } - let valueData = try pairDecoder.getData(for: valueDataType, path: path) - guard content[key] != nil else { - content[key] = [valueData] - continue - } - content[key]!.append(valueData) - } - let mapped = content.mapValues { parts -> Data in - guard parts.count > 1 else { - return parts[0] - } - /// We need to prepend the length of each element - /// so that `KeyedEncoder` can decode it correctly - return parts.map { - $0.count.variableLengthEncoding + $0 - }.joinedData - } - super.init(content: mapped, path: path, info: info) - } -} diff --git a/Sources/BinaryCodable/Decoding/Protobuf/Dictionary/ProtoDictUnkeyedDecoder.swift b/Sources/BinaryCodable/Decoding/Protobuf/Dictionary/ProtoDictUnkeyedDecoder.swift deleted file mode 100644 index 3e8c5b5..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/Dictionary/ProtoDictUnkeyedDecoder.swift +++ /dev/null @@ -1,91 +0,0 @@ -import Foundation - -final class ProtoDictUnkeyedDecoder: AbstractDecodingNode, UnkeyedDecodingContainer { - - let elements: [(dataType: DataType, data: Data)] - - init(data: Data, path: [CodingKey], info: UserInfo) throws { - let decoder = DataDecoder(data: data) - var elements = [(dataType: DataType, data: Data)]() - while decoder.hasMoreBytes { - let pairData = try decoder.getData(for: .variableLength, path: path) - let pairDecoder = DataDecoder(data: pairData) - - let (keyField, keyDataType) = try DecodingKey.decodeProto(from: pairDecoder, path: path) - guard case .intKey(1) = keyField else { - throw ProtobufDecodingError.unexpectedDictionaryKey - } - let keyData = try pairDecoder.getData(for: keyDataType, path: path) - elements.append((dataType: keyDataType, data: keyData)) - - let (valueField, valueDataType) = try DecodingKey.decodeProto(from: pairDecoder, path: path) - guard case .intKey(2) = valueField else { - throw ProtobufDecodingError.unexpectedDictionaryKey - } - let valueData = try pairDecoder.getData(for: valueDataType, path: path) - elements.append((dataType: valueDataType, data: valueData)) - } - self.elements = elements - super.init(path: path, info: info) - } - - var count: Int? { - elements.count - } - - var isAtEnd: Bool { - currentIndex == elements.count - } - - var currentIndex: Int = 0 - - func decodeNil() -> Bool { - return false - } - - private var currentElement: (dataType: DataType, data: Data) { - elements[currentIndex] - } - - private func getCurrentElementVariableLengthData() throws -> Data { - let element = currentElement - guard element.dataType == .variableLength else { - throw ProtobufDecodingError.unexpectedDictionaryKey - } - return element.data - } - - func decode(_ type: T.Type) throws -> T where T : Decodable { - defer { currentIndex += 1 } - if let Primitive = type as? DecodablePrimitive.Type { - let element = currentElement - guard element.dataType == Primitive.dataType else { - throw ProtobufDecodingError.unexpectedDictionaryKey - } - if let ProtoType = type as? ProtobufDecodable.Type { - return try ProtoType.init(fromProtobuf: element.data, path: codingPath) as! T - } - throw ProtobufDecodingError.unsupported(type: type) - } - let decoder = DataDecoder(data: try getCurrentElementVariableLengthData()) - let node = ProtoDecodingNode(decoder: decoder, path: codingPath, info: userInfo) - return try T.init(from: node) - } - - func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - defer { currentIndex += 1 } - let data = try getCurrentElementVariableLengthData() - let container = try ProtoKeyedDecoder(data: data, path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) - } - - func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - defer { currentIndex += 1 } - let data = try getCurrentElementVariableLengthData() - return try ProtoUnkeyedDecoder(data: data, path: codingPath, info: userInfo) - } - - func superDecoder() throws -> Decoder { - throw ProtobufDecodingError.superNotSupported - } -} diff --git a/Sources/BinaryCodable/Decoding/Protobuf/OneOf/OneOfAssociatedValuesDecoder.swift b/Sources/BinaryCodable/Decoding/Protobuf/OneOf/OneOfAssociatedValuesDecoder.swift deleted file mode 100644 index 613d451..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/OneOf/OneOfAssociatedValuesDecoder.swift +++ /dev/null @@ -1,70 +0,0 @@ -import Foundation - -/** - A keyed decoder specifically to decode the associated value within a `ProtobufOneOf` type. - - The decoder receives the data associated with the enum case from `OneOfKeyedDecoder`, - and allows exactly one associated value key (`_0`) to be decoded from the data. - - The decoder allows only one call to `decode(_:forKey:)`, all other access will fail. - */ -final class OneOfAssociatedValuesDecoder: AbstractDecodingNode, KeyedDecodingContainerProtocol where Key: CodingKey { - - private let data: Data - - init(data: Data, path: [CodingKey], info: UserInfo) { - self.data = data - super.init(path: path, info: info) - } - - var allKeys: [Key] { - [Key(stringValue: "_0")!] - } - - func contains(_ key: Key) -> Bool { - return true - } - - func decodeNil(forKey key: Key) throws -> Bool { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `decodeNil(forKey:)` while decoding associated value for a `ProtobufOneOf` type") - } - - func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { - if let _ = type as? DecodablePrimitive.Type { - if let ProtoType = type as? ProtobufDecodable.Type { - if !data.isEmpty { - return try ProtoType.init(fromProtobuf: data, path: codingPath + [key]) as! T - } else { - return ProtoType.zero as! T - } - } - throw ProtobufDecodingError.unsupported(type: type) - } else if type is AnyDictionary.Type { - let node = ProtoDictDecodingNode(data: data, path: codingPath, info: userInfo) - return try T.init(from: node) - } - let node = ProtoDecodingNode(data: data, path: codingPath, info: userInfo) - return try T.init(from: node) - } - - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `nestedContainer(keyedBy:forKey:)` while decoding associated value for a `ProtobufOneOf` type") - } - - func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `nestedUnkeyedContainer(forKey:)` while decoding associated value for a `ProtobufOneOf` type") - } - - func superDecoder() throws -> Decoder { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `superDecoder()` while decoding associated value for a `ProtobufOneOf` type") - } - - func superDecoder(forKey key: Key) throws -> Decoder { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `superDecoder(forKey:)` while decoding associated value for a `ProtobufOneOf` type") - } -} diff --git a/Sources/BinaryCodable/Decoding/Protobuf/OneOf/OneOfDecodingNode.swift b/Sources/BinaryCodable/Decoding/Protobuf/OneOf/OneOfDecodingNode.swift deleted file mode 100644 index fa1120f..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/OneOf/OneOfDecodingNode.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation - -typealias KeyedDataCallback = ((CodingKey) -> Data?) - -/** - A decoding node specifically to decode protobuf `OneOf` structures. - - The node receives all data from the parent container, and hands it to a `OneOfKeyedDecoder`, - which then decodes the keyed values within the enum. - - Attempts to access other containers (`keyed` or `single`) will throw an error. - */ -final class OneOfDecodingNode: AbstractDecodingNode, Decoder { - - private let content: [DecodingKey: Data] - - init(content: [DecodingKey: Data], path: [CodingKey], info: UserInfo) { - self.content = content - super.init(path: path, info: info) - } - - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { - let container = OneOfKeyedDecoder(content: content, path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) - } - - func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw ProtobufDecodingError.invalidAccess( - "Attempt to access unkeyedContainer() for a type conforming to ProtobufOneOf") - } - - func singleValueContainer() throws -> SingleValueDecodingContainer { - throw ProtobufDecodingError.invalidAccess( - "Attempt to access singleValueContainer() for a type conforming to ProtobufOneOf") - } -} diff --git a/Sources/BinaryCodable/Decoding/Protobuf/OneOf/OneOfKeyedDecoder.swift b/Sources/BinaryCodable/Decoding/Protobuf/OneOf/OneOfKeyedDecoder.swift deleted file mode 100644 index 3ee2175..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/OneOf/OneOfKeyedDecoder.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation - -/** - A keyed decoder specifically to decode an enum of type `ProtobufOneOf`. - - The decoder only allows a call to `nestedContainer(keyedBy:forKey:)` - to decode the associated value of the `OneOf`, all other access will fail with an error. - - The decoder receives all key-value pairs from the parent node (passed through `OneOfDecodingNode`), - in order to select the appropriate data required to decode the enum (`OneOf` types share the field values with the enclosing message). - */ -final class OneOfKeyedDecoder: AbstractDecodingNode, KeyedDecodingContainerProtocol where Key: CodingKey { - - private let content: [DecodingKey: Data] - - init(content: [DecodingKey: Data], path: [CodingKey], info: UserInfo) { - self.content = content - super.init(path: path, info: info) - } - - var allKeys: [Key] { - content.keys.compactMap { key in - switch key { - case .intKey(let value): - return Key(intValue: value) - case .stringKey(let value): - return Key(stringValue: value) - } - } - } - - func contains(_ key: Key) -> Bool { - content.keys.contains { $0.isEqual(to: key) } - } - - func decodeNil(forKey key: Key) throws -> Bool { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `decodeNil(forKey:)` while decoding `ProtobufOneOf` enum") - } - - func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `decode(_,forKey)` while decoding `ProtobufOneOf` enum") - } - - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - let data = content.first(where: { $0.key.isEqual(to: key) })?.value ?? Data() - let container = OneOfAssociatedValuesDecoder(data: data, path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) - } - - func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `nestedUnkeyedContainer(forKey:)` while decoding `ProtobufOneOf` enum") - } - - func superDecoder() throws -> Decoder { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `superDecoder()` while decoding `ProtobufOneOf` enum") - } - - func superDecoder(forKey key: Key) throws -> Decoder { - throw ProtobufDecodingError.invalidAccess( - "Unexpected call to `superDecoder(forKey:)` while decoding `ProtobufOneOf` enum") - } -} diff --git a/Sources/BinaryCodable/Decoding/Protobuf/ProtoDecodingNode.swift b/Sources/BinaryCodable/Decoding/Protobuf/ProtoDecodingNode.swift deleted file mode 100644 index a242023..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/ProtoDecodingNode.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation - -class ProtoDecodingNode: AbstractDecodingNode, Decoder { - - let storage: Storage - - init(data: Data, path: [CodingKey], info: UserInfo) { - self.storage = .data(data) - super.init(path: path, info: info) - } - - init(decoder: BinaryStreamProvider, path: [CodingKey], info: UserInfo) { - self.storage = .decoder(decoder) - super.init(path: path, info: info) - } - - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { - let container = try ProtoKeyedDecoder(data: storage.useAsData(path: codingPath), path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) - } - - func unkeyedContainer() throws -> UnkeyedDecodingContainer { - return try ProtoUnkeyedDecoder(data: storage.useAsData(path: codingPath), path: codingPath, info: userInfo) - } - - func singleValueContainer() throws -> SingleValueDecodingContainer { - return ProtoValueDecoder( - data: storage.useAsDecoder(), - path: codingPath, - info: userInfo) - } -} diff --git a/Sources/BinaryCodable/Decoding/Protobuf/ProtoKeyedDecoder.swift b/Sources/BinaryCodable/Decoding/Protobuf/ProtoKeyedDecoder.swift deleted file mode 100644 index 48249da..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/ProtoKeyedDecoder.swift +++ /dev/null @@ -1,107 +0,0 @@ -import Foundation - -class ProtoKeyedDecoder: AbstractDecodingNode, KeyedDecodingContainerProtocol where Key: CodingKey { - - let content: [DecodingKey: Data] - - init(data: Data, path: [CodingKey], info: UserInfo) throws { - let decoder = DataDecoder(data: data) - var content = [DecodingKey: [Data]]() - while decoder.hasMoreBytes { - let (key, dataType) = try DecodingKey.decodeProto(from: decoder, path: path) - - let data = try decoder.getData(for: dataType, path: path) - - guard content[key] != nil else { - content[key] = [data] - continue - } - content[key]!.append(data) - } - self.content = content.mapValues { parts in - guard parts.count > 1 else { - return parts[0] - } - /// We need to prepend the length of each element - /// so that `KeyedEncoder` can decode it correctly - return parts.map { - $0.count.variableLengthEncoding + $0 - }.joinedData - } - super.init(path: path, info: info) - } - - init(content: [DecodingKey: Data], path: [CodingKey], info: UserInfo) { - self.content = content - super.init(path: path, info: info) - } - - var allKeys: [Key] { - content.keys.compactMap { key in - switch key { - case .intKey(let value): - return Key(intValue: value) - case .stringKey(let value): - return Key(stringValue: value) - } - } - } - - func contains(_ key: Key) -> Bool { - content.keys.contains { $0.isEqual(to: key) } - } - - private func getDataIfAvailable(forKey key: CodingKey) -> Data? { - content.first(where: { $0.key.isEqual(to: key) })?.value - } - - func getData(forKey key: CodingKey) -> Data { - getDataIfAvailable(forKey: key) ?? Data() - } - - func decodeNil(forKey key: Key) throws -> Bool { - !contains(key) - } - - func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { - if type is ProtobufOneOf.Type { - let node = OneOfDecodingNode(content: content, path: codingPath, info: userInfo) - return try T.init(from: node) - } - let data = getDataIfAvailable(forKey: key) - if let _ = type as? DecodablePrimitive.Type { - if let ProtoType = type as? ProtobufDecodable.Type { - if let data = data { - return try ProtoType.init(fromProtobuf: data, path: codingPath + [key]) as! T - } else { - return ProtoType.zero as! T - } - } - throw ProtobufDecodingError.unsupported(type: type) - } else if type is AnyDictionary.Type { - let node = ProtoDictDecodingNode(data: data ?? Data(), path: codingPath, info: userInfo) - return try T.init(from: node) - } - let node = ProtoDecodingNode(data: data ?? Data(), path: codingPath, info: userInfo) - return try T.init(from: node) - } - - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - let data = getData(forKey: key) - let container = try ProtoKeyedDecoder(data: data, path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) - } - - func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - let data = getData(forKey: key) - return try ProtoUnkeyedDecoder(data: data, path: codingPath, info: userInfo) - } - - func superDecoder() throws -> Decoder { - throw ProtobufDecodingError.superNotSupported - } - - func superDecoder(forKey key: Key) throws -> Decoder { - throw ProtobufDecodingError.superNotSupported - } -} diff --git a/Sources/BinaryCodable/Decoding/Protobuf/ProtoUnkeyedDecoder.swift b/Sources/BinaryCodable/Decoding/Protobuf/ProtoUnkeyedDecoder.swift deleted file mode 100644 index e3944a5..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/ProtoUnkeyedDecoder.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation - -final class ProtoUnkeyedDecoder: AbstractDecodingNode, UnkeyedDecodingContainer { - - private let decoder: BinaryStreamProvider - - init(data: Data, path: [CodingKey], info: UserInfo) throws { - self.decoder = DataDecoder(data: data) - super.init(path: path, info: info) - } - - var count: Int? { - nil - } - - var isAtEnd: Bool { - !decoder.hasMoreBytes - } - - var currentIndex: Int = 0 - - func decodeNil() -> Bool { - return false - } - - func decode(_ type: T.Type) throws -> T where T : Decodable { - defer { currentIndex += 1 } - if let Primitive = type as? DecodablePrimitive.Type { - let dataType = Primitive.dataType - let data = try decoder.getData(for: dataType, path: codingPath) - if let ProtoType = type as? ProtobufDecodable.Type { - return try ProtoType.init(fromProtobuf: data, path: codingPath) as! T - } - throw ProtobufDecodingError.unsupported(type: type) - } - let node = ProtoDecodingNode(decoder: decoder, path: codingPath, info: userInfo) - return try T.init(from: node) - } - - func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - currentIndex += 1 - let data = try decoder.getData(for: .variableLength, path: codingPath) - let container = try ProtoKeyedDecoder(data: data, path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) - } - - func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - currentIndex += 1 - let data = try decoder.getData(for: .variableLength, path: codingPath) - return try ProtoUnkeyedDecoder(data: data, path: codingPath, info: userInfo) - } - - func superDecoder() throws -> Decoder { - currentIndex += 1 - return ProtoDecodingNode(decoder: decoder, path: codingPath, info: userInfo) - } -} diff --git a/Sources/BinaryCodable/Decoding/Protobuf/ProtoValueDecoder.swift b/Sources/BinaryCodable/Decoding/Protobuf/ProtoValueDecoder.swift deleted file mode 100644 index 2a30197..0000000 --- a/Sources/BinaryCodable/Decoding/Protobuf/ProtoValueDecoder.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Foundation - -final class ProtoValueDecoder: AbstractDecodingNode, SingleValueDecodingContainer { - - let data: BinaryStreamProvider - - init(data: BinaryStreamProvider, path: [CodingKey], info: UserInfo) { - self.data = data - super.init(path: path, info: info) - } - - func decodeNil() -> Bool { - false - } - - func decode(_ type: T.Type) throws -> T where T : Decodable { - guard let Primitive = type as? DecodablePrimitive.Type else { - let node = ProtoDecodingNode(decoder: data, path: codingPath, info: userInfo) - return try T.init(from: node) - } - - guard let ProtoType = Primitive as? ProtobufDecodable.Type else { - throw ProtobufDecodingError.unsupported(type: type) - } - guard self.data.hasMoreBytes else { - return ProtoType.zero as! T - } - let data = try self.data.getData(for: Primitive.dataType, path: codingPath) - return try ProtoType.init(fromProtobuf: data, path: codingPath) as! T - } -} diff --git a/Sources/BinaryCodable/Decoding/UnkeyedDecoder.swift b/Sources/BinaryCodable/Decoding/UnkeyedDecoder.swift index d324434..4593498 100644 --- a/Sources/BinaryCodable/Decoding/UnkeyedDecoder.swift +++ b/Sources/BinaryCodable/Decoding/UnkeyedDecoder.swift @@ -2,104 +2,61 @@ import Foundation final class UnkeyedDecoder: AbstractDecodingNode, UnkeyedDecodingContainer { - private let decoder: BinaryStreamProvider + var count: Int? { data.count } - private let nilIndices: Set + var isAtEnd: Bool { currentIndex >= data.count } - init(data: Data, path: [CodingKey], info: UserInfo) throws { - let decoder = DataDecoder(data: data) - self.decoder = decoder - if info.has(.prependNilIndicesForUnkeyedContainers) { - let nilIndicesCount = try decoder.getVarint(path: path) - guard nilIndicesCount >= 0 else { - throw DecodingError.invalidDataSize(path) - } - self.nilIndices = try (0.. Data? { + guard currentIndex < data.count else { + throw DecodingError.corrupted("No more elements to decode", codingPath: codingPath) + } + return data[currentIndex] } - func decodeNil() throws -> Bool { - guard prependNilIndexSetForUnkeyedContainers else { - fatalError("Use option `containsNilIndexSetForUnkeyedContainers` to use `decodeNil()`") - } - guard nilIndices.contains(currentIndex) else { - return false - } + /// Get the next element and advance the index. + private func nextElement() throws -> Data? { + let element = try ensureNextElement() currentIndex += 1 - return true + return element } - func decode(_ type: T.Type) throws -> T where T : Decodable { - defer { currentIndex += 1 } - return try wrapError { - if type is AnyOptional.Type { - let node = DecodingNode(decoder: decoder, isOptional: true, path: codingPath, info: userInfo, isInUnkeyedContainer: true) - return try T.init(from: node) - } else if let Primitive = type as? DecodablePrimitive.Type { - let dataType = Primitive.dataType - let data = try decoder.getData(for: dataType, path: codingPath) - return try Primitive.init(decodeFrom: data, path: codingPath) as! T - } else { - let node = DecodingNode(decoder: decoder, path: codingPath, info: userInfo, isInUnkeyedContainer: true) - return try T.init(from: node) - } - } + private func nextNode() throws -> DecodingNode { + let element = try nextElement() + return try DecodingNode(data: element, parentDecodedNil: true, codingPath: codingPath, userInfo: userInfo) } func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - currentIndex += 1 - return try wrapError { - let data = try decoder.getData(for: .variableLength, path: codingPath) - let container = try KeyedDecoder(data: data, path: codingPath, info: userInfo) - return KeyedDecodingContainer(container) - } + KeyedDecodingContainer(try nextNode().container(keyedBy: type)) } func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - currentIndex += 1 - return try wrapError { - let data = try decoder.getData(for: .variableLength, path: codingPath) - return try UnkeyedDecoder(data: data, path: codingPath, info: userInfo) - } + try nextNode().unkeyedContainer() } func superDecoder() throws -> Decoder { - currentIndex += 1 - return DecodingNode(decoder: decoder, path: codingPath, info: userInfo, isInUnkeyedContainer: true) + try nextNode() } - private func wrapError(_ block: () throws -> T) throws -> T { - do { - return try block() - } catch DecodingError.dataCorrupted(let context) { - var codingPath = codingPath - codingPath.append(AnyCodingKey(intValue: currentIndex)) - codingPath.append(contentsOf: context.codingPath) - let newContext = DecodingError.Context( - codingPath: codingPath, - debugDescription: context.debugDescription, - underlyingError: context.underlyingError - ) - throw DecodingError.dataCorrupted(newContext) + func decodeNil() throws -> Bool { + if try ensureNextElement() == nil { + currentIndex += 1 + return true } + return false + } + + func decode(_ type: T.Type) throws -> T where T : Decodable { + let element = try nextElement() + return try decode(element: element, type: type, codingPath: codingPath) } } diff --git a/Sources/BinaryCodable/Decoding/ValueDecoder.swift b/Sources/BinaryCodable/Decoding/ValueDecoder.swift index 41b242e..fd3961a 100644 --- a/Sources/BinaryCodable/Decoding/ValueDecoder.swift +++ b/Sources/BinaryCodable/Decoding/ValueDecoder.swift @@ -2,59 +2,18 @@ import Foundation final class ValueDecoder: AbstractDecodingNode, SingleValueDecodingContainer { - private var storage: Storage + private let data: Data? - private let isOptional: Bool - - private let isInUnkeyedContainer: Bool - - init(storage: Storage, isOptional: Bool, isInUnkeyedContainer: Bool, path: [CodingKey], info: UserInfo) { - self.storage = storage - self.isOptional = isOptional - self.isInUnkeyedContainer = isInUnkeyedContainer - super.init(path: path, info: info) - } - - private func asDecoder() -> BinaryStreamProvider { - let decoder = self.storage.useAsDecoder() - self.storage = .decoder(decoder) - return decoder + init(data: Data?, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { + self.data = data + super.init(parentDecodedNil: false, codingPath: codingPath, userInfo: userInfo) } func decodeNil() -> Bool { - do { - let byte = try asDecoder().getByte(path: codingPath) - return byte == 0 - } catch { - return false - } + data == nil } func decode(_ type: T.Type) throws -> T where T : Decodable { - if type is AnyOptional.Type { - let node = DecodingNode(storage: storage, isOptional: true, path: codingPath, info: userInfo) - return try T.init(from: node) - } else if let Primitive = type as? DecodablePrimitive.Type { - let data: Data - if !isInUnkeyedContainer, Primitive.dataType == .variableLength, !isOptional { - switch storage { - case .data(let d): - data = d - case .decoder(let decoder): - if let d = decoder as? DataDecoder { - data = d.getAllData() - } else { - data = try decoder.getData(for: Primitive.dataType, path: codingPath) - } - } - } else { - let decoder = asDecoder() - data = try decoder.getData(for: Primitive.dataType, path: codingPath) - } - return try Primitive.init(decodeFrom: data, path: codingPath) as! T - } else { - let node = DecodingNode(storage: storage, path: codingPath, info: userInfo) - return try T.init(from: node) - } + try decode(element: data, type: type, codingPath: codingPath) } } diff --git a/Sources/BinaryCodable/Encoding/AbstractEncodingNode.swift b/Sources/BinaryCodable/Encoding/AbstractEncodingNode.swift index 30e91d2..fff08bd 100644 --- a/Sources/BinaryCodable/Encoding/AbstractEncodingNode.swift +++ b/Sources/BinaryCodable/Encoding/AbstractEncodingNode.swift @@ -1,7 +1,21 @@ import Foundation +/** + A class that provides encoding functions for all encoding containers. + */ class AbstractEncodingNode: AbstractNode { + let needsLengthData: Bool + + /** + - Parameter codingPath: The path to get to this point in encoding or decoding + - Parameter userInfo: Contextual information set by the user + */ + init(needsLengthData: Bool, codingPath: [CodingKey], userInfo: UserInfo) { + self.needsLengthData = needsLengthData + super.init(codingPath: codingPath, userInfo: userInfo) + } + /** Sort keyed data in the binary representation. @@ -15,13 +29,16 @@ class AbstractEncodingNode: AbstractNode { - Note: The default value for this option is `false`. */ var sortKeysDuringEncoding: Bool { - userInfo.has(.sortKeys) + userInfo[BinaryEncoder.userInfoSortKey] as? Bool ?? false } - - var containsOptional: Bool - init(path: [CodingKey], info: UserInfo, optional: Bool) { - self.containsOptional = optional - super.init(path: path, info: info) + func encodeValue(_ value: T, needsLengthData: Bool) throws -> EncodableContainer where T : Encodable { + if T.self is EncodablePrimitive.Type, let base = value as? EncodablePrimitive { + return PrimitiveEncodingContainer(wrapped: base, needsLengthData: needsLengthData) + } else { + let encoder = EncodingNode(needsLengthData: needsLengthData, codingPath: codingPath, userInfo: userInfo) + try value.encode(to: encoder) + return encoder + } } } diff --git a/Sources/BinaryCodable/Encoding/CodingKeyWrapper.swift b/Sources/BinaryCodable/Encoding/CodingKeyWrapper.swift deleted file mode 100644 index 2b264e0..0000000 --- a/Sources/BinaryCodable/Encoding/CodingKeyWrapper.swift +++ /dev/null @@ -1,169 +0,0 @@ -import Foundation - -/** - The largest value (inclusive) for a valid integer coding key. - - The value is an integer with all but the first 5 MSB set to `1`. - - On 64 bit systems, the value is: - - Hex: `0x07FFFFFFFFFFFFFF` - - Decimal: `576460752303423487` - - Mathmatically: `2^59-1` - - On 32 bit systems, the value is: - - Hex: `0x07FFFFFF` - - Decimal: `134217727` - - Mathmatically: `2^27-1` - */ -private let intKeyUpperBound = Int(bitPattern: ~UInt(0) >> 5) - -/** - The smallest value (inclusive) for a valid integer coding key. - - The value is an integer with only the first 5 MSB set to `1`. - - On 64 bit systems, the value is: - - Hex: `0xF800000000000000` - - Decimal: `-576460752303423488` - - Mathmatically: `-2^59` - - On 32 bit systems, the value is: - - Hex: `0xF8000000` - - Decimal: `-134217728` - - Mathmatically: `-2^27` - */ -private let intKeyLowerBound = Int(bitPattern: 0xF8 << (UInt.bitWidth - 8)) - - -protocol CodingKeyWrapper { - - func encode(for dataType: DataType) -> Data - - var stringValue: String { get } -} - -/** - A wrapper around a coding key to allow usage in dictionaries. - */ -struct MixedCodingKeyWrapper: CodingKeyWrapper { - - private let intValue: Int? - - let stringValue: String - - /** - Create a wrapper around a coding key. - */ - init(_ codingKey: CodingKey) { - if let int = codingKey.intValue { - // Check that integer key is in valid range - if int > intKeyUpperBound || int < intKeyLowerBound { - fatalError("Integer key \(int) is out of range [\(intKeyLowerBound)...\(intKeyUpperBound)] for coding key \(codingKey.stringValue)") - } - } - self.intValue = codingKey.intValue - self.stringValue = codingKey.stringValue - } - - init(intValue: Int?, stringValue: String) { - self.intValue = intValue - self.stringValue = stringValue - } - - /** - Encode a coding key for a value of a specific type. - - Parameter dataType: The data type of the value associated with this key. - - Returns: The encoded data of the coding key, the data type and the key type indicator. - */ - func encode(for dataType: DataType) -> Data { - guard let intValue = intValue else { - return encodeStringValue(for: dataType) - } - return encode(int: intValue, for: dataType, isStringKey: false) - } - - /** - Encode the string value of the coding key. - - Parameter dataType: The data type of the value associated with this key. - - Returns: The encoded data of the coding key, the data type and the key type indicator. - */ - private func encodeStringValue(for dataType: DataType) -> Data { - let count = stringValue.count - let data = encode(int: count, for: dataType, isStringKey: true) - // It's assumed that all property names can be represented in UTF-8 - return data + stringValue.data(using: .utf8)! - } - - /** - Encode an integer value with a data type and the string key indicator. - - - Note: The integer is encoded using variable-length encoding. - - Note: Integer range is checked in constructor. It's also assumed that a property name will not exceed 2^59 characters. - - Parameter int: The integer (either string key length or integer key) to encode. - - Parameter dataType: The data type of the value associated with this key. - - Returns: The encoded data of the integer, the data type and the key type indicator. - */ - private func encode(int: Int, for dataType: DataType, isStringKey: Bool) -> Data { - // Bit 0-2 are the data type - // Bit 3 is the string key indicator - // Remaining bits are the integer - let mixed = (int << 4) | dataType.rawValue | (isStringKey ? 0x08 : 0x00) - return mixed.variableLengthEncoding - } -} - -extension MixedCodingKeyWrapper: Equatable { - - static func == (lhs: MixedCodingKeyWrapper, rhs: MixedCodingKeyWrapper) -> Bool { - lhs.stringValue == rhs.stringValue - } -} - -extension MixedCodingKeyWrapper: Hashable { - - func hash(into hasher: inout Hasher) { - if let int = intValue { - hasher.combine(int) - } else { - hasher.combine(stringValue) - } - } -} - -extension MixedCodingKeyWrapper: Comparable { - - static func < (lhs: MixedCodingKeyWrapper, rhs: MixedCodingKeyWrapper) -> Bool { - if let lhsInt = lhs.intValue, let rhsInt = rhs.intValue { - return lhsInt < rhsInt - } - return lhs.stringValue < rhs.stringValue - } -} - -extension MixedCodingKeyWrapper: EncodingContainer { - - var data: Data { - if let intValue = intValue { - return UInt64(intValue).protobufData() - } - return stringValue.data(using: .utf8)! - } - - var dataType: DataType { - intValue != nil ? Int.dataType: String.dataType - } - - var isEmpty: Bool { false } -} diff --git a/Sources/BinaryCodable/Encoding/CodingOption.swift b/Sources/BinaryCodable/Encoding/CodingOption.swift deleted file mode 100644 index 8ab8f6c..0000000 --- a/Sources/BinaryCodable/Encoding/CodingOption.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -enum CodingOption: String { - - case sortKeys = "sort" - - case prependNilIndicesForUnkeyedContainers = "nilIndices" - - var infoKey: CodingUserInfoKey { - .init(rawValue: rawValue)! - } -} - -extension UserInfo { - - func has(_ option: CodingOption) -> Bool { - (self[option.infoKey] as? Bool) == true - } -} diff --git a/Sources/BinaryCodable/Encoding/EncodableContainer.swift b/Sources/BinaryCodable/Encoding/EncodableContainer.swift new file mode 100644 index 0000000..5fa668d --- /dev/null +++ b/Sources/BinaryCodable/Encoding/EncodableContainer.swift @@ -0,0 +1,89 @@ +import Foundation + +/** + A protocol adopted by primitive types for encoding. + */ +protocol EncodableContainer { + + /// Indicate if the container needs to have a length prepended + var needsLengthData: Bool { get } + + /// Indicate if the container can encode nil + var needsNilIndicator: Bool { get } + + /// Indicate if the container encodes nil + /// - Note: This property must not be `true` if `needsNilIndicator` is set to `false` + var isNil: Bool { get } + + /** + Provide the data encoded in the container + - Note: No length information must be included + - Note: This function is only called if `isNil` is false + */ + func containedData() throws -> Data +} + +extension EncodableContainer { + + /** + + */ + func completeData(with key: CodingKey, codingPath: [CodingKey]) throws -> Data { + try key.keyData(codingPath: codingPath) + completeData() + } + + /** + The full data encoded in the container, including nil indicator and length, if needed + */ + func completeData() throws -> Data { + guard !isNil else { + // A nil value always means: + // - That the length is zero + // - That a nil indicator is needed + return Data([0x01]) + } + let data = try containedData() + if needsLengthData { + // It doesn't matter if `needsNilIndicator` is true or false + // Length always includes it + return data.count.lengthData + data + } + if needsNilIndicator { + return Data([0x00]) + data + } + return data + } +} + +private extension Int { + + /// Encodes the integer as the length of a nil/length indicator + var lengthData: Data { + // The first bit (LSB) is the `nil` bit (0) + // The rest is the length, encoded as a varint + (UInt64(self) << 1).encodedData + } +} + +private extension CodingKey { + + func keyData(codingPath: [CodingKey]) throws -> Data { + // String or Int key bit + // Length of String key or Int key as varint + // String Key Data + guard let intValue else { + let stringData = stringValue.data(using: .utf8)! + // Set String bit to 1 + let lengthValue = (UInt64(stringData.count) << 1) + 0x01 + let lengthData = lengthValue.encodedData + return lengthData + stringData + } + guard intValue >= 0 else { + throw EncodingError.invalidValue(intValue, .init(codingPath: codingPath + [self], debugDescription: "Invalid integer value for coding key")) + } + // For integer keys: + // The LSB is set to 0 + // Encode 2 * intValue + return intValue.lengthData + } +} diff --git a/Sources/BinaryCodable/Encoding/EncodedPrimitive.swift b/Sources/BinaryCodable/Encoding/EncodedPrimitive.swift deleted file mode 100644 index 665cc44..0000000 --- a/Sources/BinaryCodable/Encoding/EncodedPrimitive.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation - -struct EncodedPrimitive: EncodingContainer { - - let dataType: DataType - - let data: Data - - let isEmpty: Bool - - init(primitive: EncodablePrimitive) throws { - self.dataType = primitive.dataType - self.data = try primitive.data() - self.isEmpty = false - } - - init(protobuf: EncodablePrimitive, excludeDefaults: Bool = false) throws { - guard protobuf.dataType.isProtobufCompatible else { - throw ProtobufEncodingError.unsupported(type: protobuf) - } - guard let value = protobuf as? ProtobufEncodable else { - throw ProtobufEncodingError.unsupported(type: protobuf) - } - if excludeDefaults && value.isZero { - self.data = .empty - self.isEmpty = true - } else { - self.data = try value.protobufData() - self.isEmpty = false - } - self.dataType = protobuf.dataType - } -} diff --git a/Sources/BinaryCodable/Encoding/EncodingContainer.swift b/Sources/BinaryCodable/Encoding/EncodingContainer.swift deleted file mode 100644 index bdbb742..0000000 --- a/Sources/BinaryCodable/Encoding/EncodingContainer.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -protocol EncodingContainer { - - var data: Data { get } - - var dataType: DataType { get } - - func encodeWithKey(_ key: CodingKeyWrapper) -> Data - - var isEmpty: Bool { get } - - var dataWithLengthInformationIfRequired: Data { get } -} - -extension EncodingContainer { - - var dataWithLengthInformationIfRequired: Data { - guard dataType == .variableLength else { - return data - } - return dataWithLengthInformation - } - - var dataWithLengthInformation: Data { - let data = self.data - return data.count.variableLengthEncoding + data - } - - - func encodeWithKey(_ key: CodingKeyWrapper) -> Data { - key.encode(for: dataType) + dataWithLengthInformationIfRequired - } -} diff --git a/Sources/BinaryCodable/Encoding/EncodingNode.swift b/Sources/BinaryCodable/Encoding/EncodingNode.swift index 944f395..3bbce37 100644 --- a/Sources/BinaryCodable/Encoding/EncodingNode.swift +++ b/Sources/BinaryCodable/Encoding/EncodingNode.swift @@ -1,61 +1,54 @@ import Foundation -class EncodingNode: AbstractEncodingNode, Encoder { - - var container: EncodingContainer? - - func wrap(container: () -> T) -> T where T: EncodingContainer { - guard self.container == nil else { - fatalError("Multiple calls to `container<>(keyedBy:)`, `unkeyedContainer()`, or `singleValueContainer()` for an encoder") +final class EncodingNode: AbstractEncodingNode, Encoder { + + private var hasMultipleCalls = false + + private var encodedValue: EncodableContainer? = nil + + private func assign(_ value: T) -> T where T: EncodableContainer { + // Prevent multiple calls to container(keyedBy:), unkeyedContainer(), or singleValueContainer() + if encodedValue == nil { + encodedValue = value + } else { + hasMultipleCalls = true } - let value = container() - self.container = value return value } - + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - let container = wrap { KeyedEncoder(path: codingPath, info: userInfo, optional: containsOptional) } - return KeyedEncodingContainer(container) + return KeyedEncodingContainer(assign(KeyedEncoder(needsLengthData: needsLengthData, codingPath: codingPath, userInfo: userInfo))) } - + func unkeyedContainer() -> UnkeyedEncodingContainer { - wrap { UnkeyedEncoder(path: codingPath, info: userInfo, optional: containsOptional) } - } - - func singleValueContainer() -> SingleValueEncodingContainer { - wrap { ValueEncoder(path: codingPath, info: userInfo, optional: containsOptional) } + return assign(UnkeyedEncoder(needsLengthData: needsLengthData, codingPath: codingPath, userInfo: userInfo)) } - func encoding(_ value: T) throws -> EncodingNode where T: Encodable { - try value.encode(to: self) - return self + func singleValueContainer() -> SingleValueEncodingContainer { + return assign(ValueEncoder(codingPath: codingPath, userInfo: userInfo)) } } -extension EncodingNode: EncodingContainer { +extension EncodingNode: EncodableContainer { - var data: Data { - container!.data + var needsNilIndicator: Bool { + // If no value is encoded, then it doesn't matter what is returned, `encodedData()` will throw an error + encodedValue?.needsNilIndicator ?? false } - var dataWithLengthInformationIfRequired: Data { - container!.dataWithLengthInformationIfRequired - } - - var dataType: DataType { - container!.dataType + var isNil: Bool { + // Returning false for missing encodedValue forces an error on `encodedData()` + encodedValue?.isNil ?? false } - func encodeWithKey(_ key: CodingKeyWrapper) -> Data { - guard let container else { - // Explicitly check for nil to prevent error with unwrapping - // when not using `encodeNil(forKey:)` for custom encoders - return Data() + func containedData() throws -> Data { + guard !hasMultipleCalls else { + throw EncodingError.invalidValue(0, .init(codingPath: codingPath, debugDescription: "Multiple calls to container(keyedBy:), unkeyedContainer(), or singleValueContainer()")) } - return container.encodeWithKey(key) - } - - var isEmpty: Bool { - container?.isEmpty ?? true + guard let encodedValue else { + throw EncodingError.invalidValue(0, .init(codingPath: codingPath, debugDescription: "No calls to container(keyedBy:), unkeyedContainer(), or singleValueContainer()")) + } + let data = try encodedValue.containedData() + return data } } diff --git a/Sources/BinaryCodable/Encoding/HashableKey.swift b/Sources/BinaryCodable/Encoding/HashableKey.swift new file mode 100644 index 0000000..6bb6b68 --- /dev/null +++ b/Sources/BinaryCodable/Encoding/HashableKey.swift @@ -0,0 +1,34 @@ +import Foundation + +/** + A wrapper for a coding key to use as a dictionary key + */ +struct HashableKey { + + let key: CodingKey +} + +extension HashableKey: Equatable { + + static func == (lhs: HashableKey, rhs: HashableKey) -> Bool { + lhs.key.stringValue == rhs.key.stringValue + } +} + +extension HashableKey: Hashable { + + func hash(into hasher: inout Hasher) { + hasher.combine(key.stringValue) + } +} + +extension HashableKey: Comparable { + + static func < (lhs: HashableKey, rhs: HashableKey) -> Bool { + guard let lhsInt = lhs.key.intValue, + let rhsInt = rhs.key.intValue else { + return lhs.key.stringValue < rhs.key.stringValue + } + return lhsInt < rhsInt + } +} diff --git a/Sources/BinaryCodable/Encoding/KeyedEncoder.swift b/Sources/BinaryCodable/Encoding/KeyedEncoder.swift index 1cfc0db..8fb460b 100644 --- a/Sources/BinaryCodable/Encoding/KeyedEncoder.swift +++ b/Sources/BinaryCodable/Encoding/KeyedEncoder.swift @@ -1,78 +1,86 @@ import Foundation final class KeyedEncoder: AbstractEncodingNode, KeyedEncodingContainerProtocol where Key: CodingKey { - - var content = [MixedCodingKeyWrapper : EncodingContainer]() - override init(path: [CodingKey], info: UserInfo, optional: Bool) { - super.init(path: path, info: info, optional: optional) - } + private var encodedValues: [HashableKey : EncodableContainer] = [:] - func assign(_ value: EncodingContainer, to key: CodingKey) { - let wrapped = MixedCodingKeyWrapper(key) - content[wrapped] = value - } - - func encodeNil(forKey key: Key) throws { - // Nothing to do, nil is ommited for keyed containers - } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - let container: EncodingContainer - if value is AnyOptional { - container = try EncodingNode(path: codingPath, info: userInfo, optional: true).encoding(value) - } else if let primitive = value as? EncodablePrimitive { - container = try wrapError(path: codingPath) { try EncodedPrimitive(primitive: primitive) } + /// Internal indicator to prevent assigning a single key multiple times + private var multiplyAssignedKey: HashableKey? = nil + + @discardableResult + private func assign(_ value: T, forKey key: CodingKey) -> T where T: EncodableContainer { + let hashableKey = HashableKey(key: key) + if encodedValues[hashableKey] != nil { + multiplyAssignedKey = hashableKey } else { - let node = EncodingNode( - path: codingPath, - info: userInfo, - optional: false) - container = try node.encoding(value) + encodedValues[hashableKey] = value } - assign(container, to: key) + return value } - + + private func assignedNode(forKey key: CodingKey) -> EncodingNode { + let node = EncodingNode(needsLengthData: true, codingPath: codingPath + [key], userInfo: userInfo) + return assign(node, forKey: key) + } + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = KeyedEncoder(path: codingPath + [key], info: userInfo, optional: false) - assign(container, to: key) - return KeyedEncodingContainer(container) + // By wrapping the nested container in a node, it adds length information to it + KeyedEncodingContainer(assignedNode(forKey: key).container(keyedBy: keyType)) } - + func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - let container = UnkeyedEncoder(path: codingPath + [key], info: userInfo, optional: false) - assign(container, to: key) - return container + // By wrapping the nested container in a node, it adds length information to it + assignedNode(forKey: key).unkeyedContainer() } - + func superEncoder() -> Encoder { - let container = EncodingNode(path: codingPath, info: userInfo, optional: false) - assign(container, to: SuperEncoderKey()) - return container + assignedNode(forKey: SuperCodingKey()) } - + func superEncoder(forKey key: Key) -> Encoder { - let container = EncodingNode(path: codingPath + [key], info: userInfo, optional: false) - assign(container, to: key) - return container + assignedNode(forKey: key) + } + + func encodeNil(forKey key: Key) throws { + // If a value is nil, then it is not encoded + // This is not consistent with the documentation of `decodeNil(forKey:)`, + // which states that when decodeNil() should fail if the key is not present. + // We could fix this by explicitly assigning a `nil` value: + // `assign(NilContainer(), forKey: key)` + // But this would cause other problems, either breaking the decoding of double optionals (e.g. Int??), + // Or by requiring an additional `nil` indicator for ALL values in keyed containers, + // which would make the format a lot less efficient + } + + func encode(_ value: T, forKey key: Key) throws where T : Encodable { + let encoded = try encodeValue(value, needsLengthData: true) + assign(encoded, forKey: key) } } +extension KeyedEncoder: EncodableContainer { -extension KeyedEncoder: EncodingContainer { + var needsNilIndicator: Bool { + false + } - var data: Data { - if sortKeysDuringEncoding { - return content.sorted { $0.key < $1.key }.map { $1.encodeWithKey($0) }.reduce(Data(), +) - } - return content.map { $1.encodeWithKey($0) }.reduce(Data(), +) + var isNil: Bool { + false } - - var dataType: DataType { - .variableLength + + func containedData() throws -> Data { + if let multiplyAssignedKey { + throw EncodingError.invalidValue(0, .init(codingPath: codingPath, debugDescription: "Multiple values assigned to key \(multiplyAssignedKey)")) + } + guard sortKeysDuringEncoding else { + return try encode(elements: encodedValues) + } + return try encode(elements: encodedValues.sorted { $0.key < $1.key }) } - var isEmpty: Bool { - !content.values.contains { !$0.isEmpty } + private func encode(elements: T) throws -> Data where T: Collection, T.Element == (key: HashableKey, value: EncodableContainer) { + try elements.map { key, value in + try value.completeData(with: key.key, codingPath: codingPath) + }.joinedData } } diff --git a/Sources/BinaryCodable/Encoding/NilContainer.swift b/Sources/BinaryCodable/Encoding/NilContainer.swift new file mode 100644 index 0000000..8dda6d9 --- /dev/null +++ b/Sources/BinaryCodable/Encoding/NilContainer.swift @@ -0,0 +1,17 @@ +import Foundation + +/** + A container to signal that `nil` was encoded in a container + */ +struct NilContainer: EncodableContainer { + + let needsLengthData = true + + let needsNilIndicator = true + + let isNil = true + + func containedData() throws -> Data { + fatalError() + } +} diff --git a/Sources/BinaryCodable/Encoding/PrimitiveEncodingContainer.swift b/Sources/BinaryCodable/Encoding/PrimitiveEncodingContainer.swift new file mode 100644 index 0000000..c4bd4db --- /dev/null +++ b/Sources/BinaryCodable/Encoding/PrimitiveEncodingContainer.swift @@ -0,0 +1,21 @@ +import Foundation + +struct PrimitiveEncodingContainer: EncodableContainer { + + let needsLengthData: Bool + + let wrapped: EncodablePrimitive + + let needsNilIndicator = false + + let isNil = false + + init(wrapped: EncodablePrimitive, needsLengthData: Bool) { + self.needsLengthData = needsLengthData + self.wrapped = wrapped + } + + func containedData() throws -> Data { + wrapped.encodedData + } +} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/AbstractProtoNode.swift b/Sources/BinaryCodable/Encoding/Protobuf/Definitions/AbstractProtoNode.swift deleted file mode 100644 index 2dfd0fe..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/AbstractProtoNode.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -class AbstractProtoNode { - - let codingPath: [CodingKey] - - let userInfo: [CodingUserInfoKey : Any] - - let encodedType: String - - var encodingError: Error? - - var isRoot: Bool { - codingPath.isEmpty - } - - init(encoding encodedType: String, path: [CodingKey], info: [CodingUserInfoKey : Any]) { - self.encodedType = encodedType - self.codingPath = path - self.userInfo = info - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/KeyedProtoEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/Definitions/KeyedProtoEncoder.swift deleted file mode 100644 index 35a51b1..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/KeyedProtoEncoder.swift +++ /dev/null @@ -1,92 +0,0 @@ -import Foundation - -final class KeyedProtoEncoder: AbstractProtoNode, KeyedEncodingContainerProtocol where Key: CodingKey { - - var content = [ProtoKeyWrapper : ProtoContainer]() - - @discardableResult - func assign(to key: CodingKey, container: () throws -> T) rethrows -> T where T: ProtoContainer { - let wrapped = ProtoKeyWrapper(key) - guard content[wrapped] == nil else { - fatalError("Multiple values encoded for key \(key)") - } - let value = try container() - content[wrapped] = value - return value - } - - func encodeNil(forKey key: Key) throws { - throw ProtobufEncodingError.nilValuesNotSupported - } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - if value is ProtobufOneOf { - // TODO: Create oneof protobuf definition - throw ProtobufEncodingError.unsupportedType("Oneof definition") - } - if let primitive = value as? EncodablePrimitive { - guard let protoPrimitive = primitive as? ProtobufEncodable else { - throw ProtobufEncodingError.unsupported(type: primitive) - } - try assign(to: key) { - try ProtoPrimitive(primitive: protoPrimitive) - } - return - } - try assign(to: key) { - try ProtoNode(encoding: "\(type(of: value))", path: codingPath, info: userInfo) - .encoding(value) - } - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = assign(to: key) { - KeyedProtoEncoder(encoding: encodedType, path: codingPath + [key], info: userInfo) - } - return KeyedEncodingContainer(container) - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - assign(to: key) { - UnkeyedProtoEncoder(encoding: encodedType, path: codingPath + [key], info: userInfo) - } - } - - func superEncoder() -> Encoder { - fatalError("Protobuf compatibility does not support encoding super") - } - - func superEncoder(forKey key: Key) -> Encoder { - fatalError("Protobuf compatibility does not support encoding super") - } -} - - -extension KeyedProtoEncoder: ProtoContainer { - - func protobufDefinition() throws -> String { - - let prefix = "message \(encodedType) {\n\n" - - let fields = try content - .sorted { $0.key < $1.key } - .map { key, value -> String in - guard let field = key.intValue else { - throw ProtobufEncodingError.missingIntegerKey(key.stringValue) - } - // TODO: Add additional message definitions - // The protobuf description needs to print also nested messages - // Currently, only the top level is shown - // This requires additional methods on the hierarchy to get all nested definitions - // within a container. - return "\(value.protoTypeName) \(key.stringValue) = \(field);" - } - .joined(separator: "\n\n").indented() - let suffix = "\n}" - return prefix + fields + suffix - } - - var protoTypeName: String { - encodedType - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoContainer.swift b/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoContainer.swift deleted file mode 100644 index 790db57..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoContainer.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -protocol ProtoContainer { - - func protobufDefinition() throws -> String - - var protoTypeName: String { get } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoKeyWrapper.swift b/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoKeyWrapper.swift deleted file mode 100644 index cd1aaaa..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoKeyWrapper.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -/** - A wrapper around a coding key to allow usage in dictionaries. - */ -struct ProtoKeyWrapper { - - let intValue: Int? - - let stringValue: String - - /** - Create a wrapper around a coding key. - */ - init(_ codingKey: CodingKey) { - self.intValue = codingKey.intValue - self.stringValue = codingKey.stringValue - } -} - -extension ProtoKeyWrapper: Equatable { - - static func == (lhs: ProtoKeyWrapper, rhs: ProtoKeyWrapper) -> Bool { - lhs.intValue == rhs.intValue - } -} - -extension ProtoKeyWrapper: Hashable { - -} - -extension ProtoKeyWrapper: Comparable { - - static func < (lhs: ProtoKeyWrapper, rhs: ProtoKeyWrapper) -> Bool { - guard let l = lhs.intValue, let r = rhs.intValue else { - return lhs.stringValue < rhs.stringValue - } - return l < r - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoNode.swift b/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoNode.swift deleted file mode 100644 index 276f432..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoNode.swift +++ /dev/null @@ -1,51 +0,0 @@ -import Foundation - -final class ProtoNode: AbstractProtoNode, Encoder { - - var container: ProtoContainer? - - private func wrap(container: () -> T) -> T where T: ProtoContainer { - let value = container() - guard self.container == nil else { - encodingError = ProtobufEncodingError.multipleContainersAccessed - return value - } - self.container = value - return value - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - let container = wrap { KeyedProtoEncoder(encoding: encodedType, path: codingPath, info: userInfo) } - return KeyedEncodingContainer(container) - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - wrap { UnkeyedProtoEncoder(encoding: encodedType, path: codingPath, info: userInfo) } - } - - func singleValueContainer() -> SingleValueEncodingContainer { - wrap { ValueProtoEncoder(encoding: encodedType, path: codingPath, info: userInfo) } - } - - func encoding(_ value: T) throws -> ProtoNode where T: Encodable { - try value.encode(to: self) - return self - } -} - -extension ProtoNode: ProtoContainer { - - func protobufDefinition() throws -> String { - if let error = encodingError { - throw error - } - guard let container = container else { - throw ProtobufEncodingError.noContainersAccessed - } - return try container.protobufDefinition() - } - - var protoTypeName: String { - container!.protoTypeName - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoPrimitive.swift b/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoPrimitive.swift deleted file mode 100644 index e125554..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ProtoPrimitive.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation - -struct ProtoPrimitive: ProtoContainer { - - let protoTypeName: String - - init(primitive: ProtobufEncodable) throws { - self.protoTypeName = primitive.protoType - } - - func protobufDefinition() throws -> String { - "" - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/UnkeyedProtoEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/Definitions/UnkeyedProtoEncoder.swift deleted file mode 100644 index fcc0e93..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/UnkeyedProtoEncoder.swift +++ /dev/null @@ -1,77 +0,0 @@ -import Foundation - -final class UnkeyedProtoEncoder: AbstractProtoNode, UnkeyedEncodingContainer { - - var count: Int { - content.count - } - - private var content = [ProtoContainer]() - - @discardableResult - private func assign(_ encoded: () throws -> T) rethrows -> T where T: ProtoContainer { - let value = try encoded() - if let existingType = content.first?.protoTypeName, existingType != value.protoTypeName { - encodingError = ProtobufEncodingError.multipleTypesInUnkeyedContainer - } - content.append(value) - return value - } - - func encodeNil() throws { - throw ProtobufEncodingError.nilValuesNotSupported - } - - func encode(_ value: T) throws where T : Encodable { - if let primitive = value as? EncodablePrimitive { - guard let protoPrimitive = primitive as? ProtobufEncodable else { - throw ProtobufEncodingError.unsupported(type: primitive) - } - try assign { - try ProtoPrimitive(primitive: protoPrimitive) - } - return - } - try assign { - try ProtoNode(encoding: "\(type(of: value))", path: codingPath, info: userInfo) - .encoding(value) - } - } - - func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = assign { - KeyedProtoEncoder(encoding: encodedType, path: codingPath, info: userInfo) - } - return KeyedEncodingContainer(container) - } - - func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - assign { - UnkeyedProtoEncoder(encoding: encodedType, path: codingPath, info: userInfo) - } - } - - func superEncoder() -> Encoder { - ProtoThrowingNode(error: .superNotSupported, path: codingPath, info: userInfo) - } -} - -extension UnkeyedProtoEncoder: ProtoContainer { - - func protobufDefinition() throws -> String { - if isRoot { - throw ProtobufEncodingError.rootIsNotKeyedContainer - } - if let error = encodingError { - throw error - } - guard let def = try content.first?.protobufDefinition() else { - throw ProtobufEncodingError.protobufDefinitionUnavailable("No value in unkeyed container to determine type") - } - return def - } - - var protoTypeName: String { - "repeated " + (content.first?.protoTypeName ?? "") - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ValueProtoEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ValueProtoEncoder.swift deleted file mode 100644 index c46d4e5..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/Definitions/ValueProtoEncoder.swift +++ /dev/null @@ -1,50 +0,0 @@ -import Foundation - -final class ValueProtoEncoder: AbstractProtoNode, SingleValueEncodingContainer { - - private var container: ProtoContainer? - - func encodeNil() throws { - throw ProtobufEncodingError.nilValuesNotSupported - } - - private func assign(_ encoded: () throws -> ProtoContainer?) rethrows { - guard container == nil else { - encodingError = ProtobufEncodingError.multipleValuesInSingleValueContainer - return - } - container = try encoded() - } - - func encode(_ value: T) throws where T : Encodable { - if let primitive = value as? EncodablePrimitive { - guard let protoPrimitive = primitive as? ProtobufEncodable else { - throw ProtobufEncodingError.unsupported(type: primitive) - } - try assign { - try ProtoPrimitive(primitive: protoPrimitive) - } - return - } - try assign { - try ProtoNode(encoding: "\(type(of: value))", path: codingPath, info: userInfo).encoding(value) - } - } -} - -extension ValueProtoEncoder: ProtoContainer { - - func protobufDefinition() throws -> String { - if isRoot { - throw ProtobufEncodingError.rootIsNotKeyedContainer - } - guard let container = container else { - throw ProtobufEncodingError.noValueInSingleValueContainer - } - return try container.protobufDefinition() - } - - var protoTypeName: String { - container?.protoTypeName ?? "No container" - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/IntKeyWrapper.swift b/Sources/BinaryCodable/Encoding/Protobuf/IntKeyWrapper.swift deleted file mode 100644 index 197675d..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/IntKeyWrapper.swift +++ /dev/null @@ -1,73 +0,0 @@ -import Foundation - -/// The largest value (inclusive) for a valid protobuf field number (536870911) -private let protoFieldUpperBound = 0x1FFFFFFF - -/// The smallest value (inclusive) for a valid integer coding key -private let protoFieldLowerBound = 1 - -/** - Wraps a protobuf field number to use as a coding key. - */ -struct IntKeyWrapper: CodingKeyWrapper { - - private let intValue: Int - - var stringValue: String { "\(intValue)" } - - static func checkFieldBounds(_ field: Int) throws { - if field < protoFieldLowerBound || field > protoFieldUpperBound { - throw ProtobufEncodingError.integerKeyOutOfRange(field) - } - } - - init(value: Int) throws { - try IntKeyWrapper.checkFieldBounds(value) - self.intValue = value - } - - init(_ key: CodingKey) throws { - guard let value = key.intValue else { - throw ProtobufEncodingError.missingIntegerKey(key.stringValue) - } - try self.init(value: value) - } - - func encode(for dataType: DataType) -> Data { - // Bit 0-2 are the data type - // Remaining bits are the integer - let mixed = (intValue << 3) | dataType.rawValue - return mixed.variableLengthEncoding - } -} - -extension IntKeyWrapper: EncodingContainer { - - var data: Data { - UInt64(intValue).protobufData() - } - - var dataType: DataType { - Int.dataType - } - - var isEmpty: Bool { - false - } -} - - -extension IntKeyWrapper: Hashable { - -} - -extension IntKeyWrapper: Equatable { - -} - -extension IntKeyWrapper: Comparable { - - static func < (lhs: IntKeyWrapper, rhs: IntKeyWrapper) -> Bool { - lhs.intValue < rhs.intValue - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/OneOf/OneOfAssociatedValuesEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/OneOf/OneOfAssociatedValuesEncoder.swift deleted file mode 100644 index 4b729af..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/OneOf/OneOfAssociatedValuesEncoder.swift +++ /dev/null @@ -1,80 +0,0 @@ -import Foundation - -/** - A protocol to abstract a `OneOfAssociatedValuesEncoder`, which can't be directly stored due to its associated type. - */ -protocol OneOfAssociatedValuesContainer { - - func getValue() throws -> EncodingContainer -} - -/** - A container to specifically encode the associated value of a `ProtobufOneOf` enum case. - - The container ignores the associated values key `_0` and only encodes the value. - - The container only allows a single call to `encode(_,forKey:)`, all other access will fail. - */ -final class OneOfAssociatedValuesEncoder: AbstractEncodingNode, KeyedEncodingContainerProtocol where Key: CodingKey { - - /// The encoded associated value - var value: EncodingContainer? - - func encodeNil(forKey key: Key) throws { - throw ProtobufEncodingError.invalidAccess( - "Unexpected call to `encodeNil(forKey:)` while encoding a `ProtobufOneOf` associated value") - } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - guard self.value == nil else { - throw ProtobufEncodingError.invalidAccess( - "Multiple calls to `encode(_,forKey:)` while encoding a `ProtobufOneOf` associated value") - } - - if let primitive = value as? EncodablePrimitive { - self.value = try wrapError(path: codingPath) { - try EncodedPrimitive(protobuf: primitive, excludeDefaults: false) - } - } else if value is AnyDictionary { - self.value = try ProtoDictEncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - } else { - self.value = try ProtoEncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - } - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = ProtoKeyedThrowingEncoder( - error: .invalidAccess("Unexpected call to `nestedContainer(keyedBy:forKey:)` while encoding a `ProtobufOneOf` associated value"), - path: codingPath + [key], info: userInfo) - return KeyedEncodingContainer(container) - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - let container = ProtoUnkeyedThrowingEncoder( - error: .invalidAccess("Unexpected call to `nestedUnkeyedContainer(forKey:)` while encoding a `ProtobufOneOf` associated value"), - path: codingPath + [key], info: userInfo) - return container - } - - func superEncoder() -> Encoder { - ProtoThrowingNode( - error: .invalidAccess("Unexpected call to `superEncoder()` while encoding a `ProtobufOneOf` associated value"), - path: codingPath, info: userInfo) - } - - func superEncoder(forKey key: Key) -> Encoder { - ProtoThrowingNode( - error: .invalidAccess("Unexpected call to `superEncoder(forKey:)` while encoding a `ProtobufOneOf` associated value"), - path: codingPath, info: userInfo) - } -} - -extension OneOfAssociatedValuesEncoder: OneOfAssociatedValuesContainer { - - func getValue() throws -> EncodingContainer { - guard let value = value else { - throw ProtobufEncodingError.noContainersAccessed - } - return value - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/OneOf/OneOfEncodingNode.swift b/Sources/BinaryCodable/Encoding/Protobuf/OneOf/OneOfEncodingNode.swift deleted file mode 100644 index ab711bb..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/OneOf/OneOfEncodingNode.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation - -/** - An encoding node specifically to encode `ProtobufOneOf` types, which require adjustments to the encoding process. - - The node wraps a `OneOfKeyedEncoder`, which encodes the enum case and associated value. - - The node allows only one call to `container(keyedBy:)`, all other access will fail. - */ -final class OneOfEncodingNode: AbstractEncodingNode, Encoder { - - /// The wrapped container encoding the enum case and associated value - var container: OneOfKeyedContainer? - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - guard self.container == nil else { - fatalError("Multiple calls to `container<>(keyedBy:)` while encoding a `ProtobufOneOf` type") - } - let container = OneOfKeyedEncoder(path: codingPath, info: userInfo, optional: false) - self.container = container - return KeyedEncodingContainer(container) - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - ProtoUnkeyedThrowingEncoder( - error: .invalidAccess("Unexpected call to `unkeyedContainer()` while encoding a `ProtobufOneOf` type"), - path: codingPath, info: userInfo) - } - - func singleValueContainer() -> SingleValueEncodingContainer { - ProtoValueThrowingEncoder( - error: .invalidAccess("Unexpected call to `singleValueContainer()` while encoding a `ProtobufOneOf` type"), - path: codingPath, info: userInfo) - } - - func encoding(_ value: T) throws -> (key: IntKeyWrapper, value: EncodingContainer) where T: Encodable { - try value.encode(to: self) - guard let container = container else { - throw ProtobufEncodingError.noContainersAccessed - } - return try container.getContent() - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/OneOf/OneOfKeyedEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/OneOf/OneOfKeyedEncoder.swift deleted file mode 100644 index 0ed91ca..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/OneOf/OneOfKeyedEncoder.swift +++ /dev/null @@ -1,74 +0,0 @@ -import Foundation - -/** - A protocol to abstract a `OneOfKeyedEncoder`, which can't be directly stored due to the associated type. - */ -protocol OneOfKeyedContainer { - - func getContent() throws -> (key: IntKeyWrapper, value: EncodingContainer) -} - -/** - A keyed encoder specifically to encode a `ProtobufOneOf` enum case and associated value. - - The encoder is wrapped by `OneOfEncodingNoder`, and itself wraps a `OneOfAssociatedValuesEncoder`. - - The encoder expects a single call to `nestedContainer(keyedBy:forKey:)`, all other access will fail. - */ -final class OneOfKeyedEncoder: AbstractEncodingNode, KeyedEncodingContainerProtocol where Key: CodingKey { - - /// The encoded data consisting of the enum case, and the associated value. - private var content: (key: IntKeyWrapper, value: OneOfAssociatedValuesContainer)? - - func encodeNil(forKey key: Key) throws { - throw ProtobufEncodingError.invalidAccess("Unexpected call to `encodeNil(forKey:)` while encoding a `ProtobufOneOf` enum") - } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - throw ProtobufEncodingError.invalidAccess("Unexpected call to `encode(_,forKey:)` while encoding a `ProtobufOneOf` enum") - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - do { - let wrapped = try wrapError(path: codingPath + [key]) { try IntKeyWrapper(key) } - let container = OneOfAssociatedValuesEncoder(path: codingPath + [key], info: userInfo, optional: false) - content = (wrapped, container) - return KeyedEncodingContainer(container) - } catch { - let container = ProtoKeyedThrowingEncoder(error: error as! ProtobufEncodingError, - path: codingPath + [key], - info: userInfo) - return KeyedEncodingContainer(container) - } - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - let container = ProtoUnkeyedThrowingEncoder( - error: .invalidAccess("Unexpected call to `encode(_,forKey:)` while encoding a `ProtobufOneOf` enum"), - path: codingPath + [key], info: userInfo) - return container - } - - func superEncoder() -> Encoder { - ProtoThrowingNode( - error: .invalidAccess("Unexpected call to `superEncoder()` while encoding a `ProtobufOneOf` enum"), - path: codingPath, info: userInfo) - } - - func superEncoder(forKey key: Key) -> Encoder { - ProtoThrowingNode( - error: .invalidAccess("Unexpected call to `superEncoder(forKey:)` while encoding a `ProtobufOneOf` enum"), - path: codingPath, info: userInfo) - } -} - -extension OneOfKeyedEncoder: OneOfKeyedContainer { - - func getContent() throws -> (key: IntKeyWrapper, value: EncodingContainer) { - guard let content = content else { - throw ProtobufEncodingError.noContainersAccessed - } - let value = try content.value.getValue() - return (content.key, value) - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictEncodingNode.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictEncodingNode.swift deleted file mode 100644 index f409013..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictEncodingNode.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -final class ProtoDictEncodingNode: ProtoEncodingNode { - - override func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - let container = wrap { ProtoDictKeyedEncoder(path: codingPath, info: userInfo, optional: false) } - return KeyedEncodingContainer(container) - } - - override func unkeyedContainer() -> UnkeyedEncodingContainer { - wrap { ProtoDictUnkeyedEncoder(path: codingPath, info: userInfo, optional: false) } - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictKeyedEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictKeyedEncoder.swift deleted file mode 100644 index e46d560..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictKeyedEncoder.swift +++ /dev/null @@ -1,92 +0,0 @@ -import Foundation - -final class ProtoDictKeyedEncoder: AbstractEncodingNode, KeyedEncodingContainerProtocol where Key: CodingKey { - - private var content = [EncodingContainer]() - - func assign(_ value: EncodingContainer, to key: EncodingContainer) { - let pair = ProtoDictPair(key: key, value: value) - content.append(pair) - } - - func encodeNil(forKey key: Key) throws { - throw ProtobufEncodingError.nilValuesNotSupported - } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - let container: EncodingContainer - if let primitive = value as? EncodablePrimitive { - container = try wrapError(path: codingPath) { try EncodedPrimitive(protobuf: primitive) } - } else { - container = try ProtoEncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - } - let wrapped = try wrapError(path: codingPath) { - try EncodedPrimitive(protobuf: key.intValue ?? key.stringValue) - } - assign(container, to: wrapped) - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - do { - let wrapped = try wrapError(path: codingPath) { - try EncodedPrimitive(protobuf: key.intValue ?? key.stringValue) - } - let container = ProtoKeyedEncoder(path: codingPath + [key], info: userInfo, optional: false) - assign(container, to: wrapped) - return KeyedEncodingContainer(container) - } catch { - let container = ProtoKeyedThrowingEncoder( - error: error as! ProtobufEncodingError, - path: codingPath, - info: userInfo) - return KeyedEncodingContainer(container) - } - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - do { - let wrapped = try wrapError(path: codingPath) { - try EncodedPrimitive(protobuf: key.intValue ?? key.stringValue) - } - let container = ProtoUnkeyedEncoder(path: codingPath + [key], info: userInfo, optional: false) - assign(container, to: wrapped) - return container - } catch { - let container = ProtoUnkeyedThrowingEncoder( - error: error as! ProtobufEncodingError, - path: codingPath, - info: userInfo) - return container - } - } - - func superEncoder() -> Encoder { - ProtoThrowingNode(error: .superNotSupported, path: codingPath, info: userInfo) - } - - func superEncoder(forKey key: Key) -> Encoder { - ProtoThrowingNode(error: .superNotSupported, path: codingPath, info: userInfo) - } -} - - -extension ProtoDictKeyedEncoder: EncodingContainer { - - func encodeWithKey(_ key: CodingKeyWrapper) -> Data { - content - .map { $0.encodeWithKey(key) } - .joinedData - } - - var data: Data { - content.map { $0.dataWithLengthInformationIfRequired }.joinedData - } - - var dataType: DataType { - .variableLength - } - - var isEmpty: Bool { - !content.contains { !$0.isEmpty } - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictPair.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictPair.swift deleted file mode 100644 index 841122e..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictPair.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation - -struct ProtoDictPair { - - let key: EncodingContainer - - let value: EncodingContainer -} - -extension ProtoDictPair: EncodingContainer { - - var data: Data { - key.encodeWithKey(try! IntKeyWrapper(value: 1)) - + value.encodeWithKey(try! IntKeyWrapper(value: 2)) - } - - var dataType: DataType { - .variableLength - } - - var isEmpty: Bool { - false - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictUnkeyedEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictUnkeyedEncoder.swift deleted file mode 100644 index fed0046..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoDictionary/ProtoDictUnkeyedEncoder.swift +++ /dev/null @@ -1,83 +0,0 @@ -import Foundation - -final class ProtoDictUnkeyedEncoder: AbstractEncodingNode, UnkeyedEncodingContainer { - - var count: Int { - content.count - } - - private var content = [ProtoDictPair]() - - private var key: EncodingContainer? - - private func assign(_ value: T) where T: EncodingContainer { - if let key = key { - let pair = ProtoDictPair(key: key, value: value) - content.append(pair) - self.key = nil - } else { - key = value - } - } - - func encodeNil() throws { - throw ProtobufEncodingError.nilValuesNotSupported - } - - func encode(_ value: T) throws where T : Encodable { - if let primitive = value as? EncodablePrimitive { - // Ensure that only same-type values are encoded - if let first = content.first { - if key == nil && first.key.dataType != primitive.dataType { - throw ProtobufEncodingError.multipleTypesInUnkeyedContainer - } - if key != nil && first.value.dataType != primitive.dataType { - throw ProtobufEncodingError.multipleTypesInUnkeyedContainer - } - } - - let node = try wrapError(path: codingPath) { - try EncodedPrimitive(protobuf: primitive) - } - assign(node) - return - } - let node = try ProtoEncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - assign(node) - } - - func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { - // Should never happen, since this container type is only used for dictionaries - fatalError() - } - - func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - // Should never happen, since this container type is only used for dictionaries - fatalError() - } - - func superEncoder() -> Encoder { - ProtoThrowingNode(error: .superNotSupported, path: codingPath, info: userInfo) - } -} - -extension ProtoDictUnkeyedEncoder: EncodingContainer { - - func encodeWithKey(_ key: CodingKeyWrapper) -> Data { - content - .map { $0.encodeWithKey(key) } - .joinedData - } - - var data: Data { - content.map { $0.dataWithLengthInformationIfRequired }.joinedData - } - - var dataType: DataType { - .variableLength - } - - var isEmpty: Bool { - content.isEmpty - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoEncodingNode.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoEncodingNode.swift deleted file mode 100644 index 0f07f0a..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoEncodingNode.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation - -class ProtoEncodingNode: AbstractEncodingNode, Encoder { - - var container: EncodingContainer? - - func wrap(container: () -> T) -> T where T: EncodingContainer { - guard self.container == nil else { - fatalError("Multiple calls to `container<>(keyedBy:)`, `unkeyedContainer()`, or `singleValueContainer()` for an encoder") - } - let value = container() - self.container = value - return value - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - let container = wrap { ProtoKeyedEncoder(path: codingPath, info: userInfo, optional: false) } - return KeyedEncodingContainer(container) - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - wrap { ProtoUnkeyedEncoder(path: codingPath, info: userInfo, optional: false) } - } - - func singleValueContainer() -> SingleValueEncodingContainer { - wrap { ProtoValueEncoder(path: codingPath, info: userInfo, optional: false) } - } - - func encoding(_ value: T) throws -> Self where T: Encodable { - try value.encode(to: self) - return self - } -} - -extension ProtoEncodingNode: EncodingContainer { - - var data: Data { - container!.data - } - - var dataType: DataType { - container!.dataType - } - - func encodeWithKey(_ key: CodingKeyWrapper) -> Data { - container!.encodeWithKey(key) - } - - var isEmpty: Bool { - container?.isEmpty ?? true - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoKeyedThrowingEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoKeyedThrowingEncoder.swift deleted file mode 100644 index e7d1948..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoKeyedThrowingEncoder.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -final class ProtoKeyedThrowingEncoder: ProtoThrowingNode, KeyedEncodingContainerProtocol where Key: CodingKey { - - func encodeNil(forKey key: Key) throws { - throw error - } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - throw error - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = ProtoKeyedThrowingEncoder(from: self) - return KeyedEncodingContainer(container) - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - ProtoUnkeyedThrowingEncoder(from: self) - } - - func superEncoder() -> Encoder { - ProtoThrowingNode(from: self) - } - - func superEncoder(forKey key: Key) -> Encoder { - ProtoThrowingNode(from: self) - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoThrowingNode.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoThrowingNode.swift deleted file mode 100644 index ff15af4..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoThrowingNode.swift +++ /dev/null @@ -1,49 +0,0 @@ -import Foundation - -/** - A class used when features are not supported for protobuf encoding. - - Any calls to encoding functions will fail with a `ProtobufEncodingError` error - */ -class ProtoThrowingNode: AbstractEncodingNode, Encoder { - - let error: ProtobufEncodingError - - init(error: ProtobufEncodingError, path: [CodingKey], info: UserInfo) { - self.error = error - super.init(path: path, info: info, optional: false) - } - - init(from node: ProtoThrowingNode) { - self.error = node.error - super.init(path: node.codingPath, info: node.userInfo, optional: false) - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - let container = ProtoKeyedThrowingEncoder(from: self) - return KeyedEncodingContainer(container) - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - ProtoUnkeyedThrowingEncoder(from: self) - } - - func singleValueContainer() -> SingleValueEncodingContainer { - ProtoValueThrowingEncoder(from: self) - } -} - -extension ProtoThrowingNode: EncodingContainer { - - var isNil: Bool { false } - - var data: Data { - .empty - } - - var dataType: DataType { - .byte - } - - var isEmpty: Bool { false } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoUnkeyedThrowingEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoUnkeyedThrowingEncoder.swift deleted file mode 100644 index 2956160..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoUnkeyedThrowingEncoder.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -final class ProtoUnkeyedThrowingEncoder: ProtoThrowingNode, UnkeyedEncodingContainer { - - var count: Int { - 0 - } - - func encodeNil() throws { - throw error - } - - func encode(_ value: T) throws where T : Encodable { - throw error - } - - func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = ProtoKeyedThrowingEncoder(from: self) - return KeyedEncodingContainer(container) - } - - func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - ProtoUnkeyedThrowingEncoder(from: self) - } - - func superEncoder() -> Encoder { - ProtoThrowingNode(from: self) - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoValueThrowingEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoValueThrowingEncoder.swift deleted file mode 100644 index 8e5d65a..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoFail/ProtoValueThrowingEncoder.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -final class ProtoValueThrowingEncoder: ProtoThrowingNode, SingleValueEncodingContainer { - - func encodeNil() throws { - throw error - } - - func encode(_ value: T) throws where T : Encodable { - throw error - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoKeyedEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoKeyedEncoder.swift deleted file mode 100644 index d4f8650..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoKeyedEncoder.swift +++ /dev/null @@ -1,101 +0,0 @@ -import Foundation - -final class ProtoKeyedEncoder: AbstractEncodingNode, KeyedEncodingContainerProtocol where Key: CodingKey { - - var content = [IntKeyWrapper : EncodingContainer]() - - func assign(_ value: EncodingContainer, to key: CodingKey) throws { - let wrapped = try IntKeyWrapper(key) - assign(value, to: wrapped) - } - - func assign(_ value: EncodingContainer, to key: IntKeyWrapper) { - content[key] = value - } - - func encodeNil(forKey key: Key) throws { - throw ProtobufEncodingError.nilValuesNotSupported - } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - var wrappedKey = try IntKeyWrapper(key) - let container: EncodingContainer - if value is ProtobufOneOf { - (wrappedKey, container) = try OneOfEncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - } else if let primitive = value as? EncodablePrimitive { - container = try wrapError(path: codingPath + [key]) { - try EncodedPrimitive(protobuf: primitive, excludeDefaults: true) - } - } else if value is AnyDictionary { - container = try ProtoDictEncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - } else { - container = try ProtoEncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - } - assign(container, to: wrappedKey) - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - do { - let wrapped = try IntKeyWrapper(key) - let container = ProtoKeyedEncoder(path: codingPath + [key], info: userInfo, optional: false) - assign(container, to: wrapped) - return KeyedEncodingContainer(container) - } catch { - let container = ProtoKeyedThrowingEncoder(error: error as! ProtobufEncodingError, - path: codingPath + [key], - info: userInfo) - return KeyedEncodingContainer(container) - } - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - do { - let wrapped = try IntKeyWrapper(key) - let container = ProtoUnkeyedEncoder(path: codingPath + [key], info: userInfo, optional: false) - assign(container, to: wrapped) - return container - } catch { - let container = ProtoUnkeyedThrowingEncoder( - error: error as! ProtobufEncodingError, - path: codingPath, - info: userInfo) - return container - } - } - - func superEncoder() -> Encoder { - ProtoThrowingNode(error: .superNotSupported, path: codingPath, info: userInfo) - } - - func superEncoder(forKey key: Key) -> Encoder { - ProtoThrowingNode(error: .superNotSupported, path: codingPath, info: userInfo) - } -} - -extension ProtoKeyedEncoder: EncodingContainer { - - private var nonEmptyValues: [(key: IntKeyWrapper, value: EncodingContainer)] { - content.filter { !$0.value.isEmpty } - } - - private var sortedKeysIfNeeded: [(key: IntKeyWrapper, value: EncodingContainer)] { - guard sortKeysDuringEncoding else { - return nonEmptyValues.map { $0 } - } - return nonEmptyValues.sorted { $0.key < $1.key } - } - - var data: Data { - sortedKeysIfNeeded.map { key, value -> Data in - value.encodeWithKey(key) - }.reduce(Data(), +) - } - - var dataType: DataType { - .variableLength - } - - var isEmpty: Bool { - !content.values.contains { !$0.isEmpty } - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoUnkeyedEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoUnkeyedEncoder.swift deleted file mode 100644 index 9a8fc78..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoUnkeyedEncoder.swift +++ /dev/null @@ -1,98 +0,0 @@ -import Foundation - -final class ProtoUnkeyedEncoder: AbstractEncodingNode, UnkeyedEncodingContainer { - - var count: Int { - content.count - } - - private var content = [EncodingContainer]() - - @discardableResult - private func assign(_ encoded: () throws -> T) rethrows -> T where T: EncodingContainer { - let value = try encoded() - content.append(value) - return value - } - - func encodeNil() throws { - throw ProtobufEncodingError.nilValuesNotSupported - } - - func encode(_ value: T) throws where T : Encodable { - if let primitive = value as? EncodablePrimitive { - // Ensure that only same-type values are encoded - - // TODO: Improve detection of same types - // The Protobuf repeated fields must have the same type - // Currently, we only check that the data type of the primitives matches, - // so different types with the same DataType would not cause an error - // This isn't a huge problem, since this could only happen if somebody would - // write a custom encoding routine, so they would probably know that this breaks - // Protobuf support. - if let first = content.first, first.dataType != primitive.dataType { - throw ProtobufEncodingError.multipleTypesInUnkeyedContainer - } - - try assign { - try wrapError(path: codingPath) { - try EncodedPrimitive(protobuf: primitive) - } - } - return - } - let node = try ProtoEncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - assign { node } - } - - func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = assign { - ProtoKeyedEncoder(path: codingPath, info: userInfo, optional: false) - } - return KeyedEncodingContainer(container) - } - - func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - assign { - ProtoUnkeyedEncoder(path: codingPath, info: userInfo, optional: false) - } - } - - func superEncoder() -> Encoder { - assign { - ProtoEncodingNode(path: codingPath, info: userInfo, optional: false) - } - } -} - -extension ProtoUnkeyedEncoder: EncodingContainer { - - private var packedProtoData: Data { - let data = self.data - return data.count.variableLengthEncoding + data - } - - func encodeWithKey(_ key: CodingKeyWrapper) -> Data { - // Don't prepend index set for protobuf, separate complex types - if let first = content.first, first.dataType == .variableLength { - // Unpacked - return content - .map { $0.encodeWithKey(key) } - .joinedData - } - // Packed - return key.encode(for: dataType) + packedProtoData - } - - var data: Data { - content.map { $0.dataWithLengthInformationIfRequired }.joinedData - } - - var dataType: DataType { - .variableLength - } - - var isEmpty: Bool { - !content.contains { !$0.isEmpty } - } -} diff --git a/Sources/BinaryCodable/Encoding/Protobuf/ProtoValueEncoder.swift b/Sources/BinaryCodable/Encoding/Protobuf/ProtoValueEncoder.swift deleted file mode 100644 index e194815..0000000 --- a/Sources/BinaryCodable/Encoding/Protobuf/ProtoValueEncoder.swift +++ /dev/null @@ -1,46 +0,0 @@ -import Foundation - -final class ProtoValueEncoder: AbstractEncodingNode, SingleValueEncodingContainer { - - private var container: EncodingContainer? - - func encodeNil() throws { - try assign { nil } - } - - private func assign(_ encoded: () throws -> EncodingContainer?) throws { - guard container == nil else { - throw ProtobufEncodingError.multipleValuesInSingleValueContainer - } - container = try encoded() - } - - func encode(_ value: T) throws where T : Encodable { - if let primitive = value as? EncodablePrimitive { - try assign { - try wrapError(path: codingPath) { - try EncodedPrimitive(protobuf: primitive, excludeDefaults: true) - } - } - return - } - try assign { - try ProtoEncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - } - } -} - -extension ProtoValueEncoder: EncodingContainer { - - var data: Data { - container?.data ?? .empty - } - - var dataType: DataType { - container!.dataType - } - - var isEmpty: Bool { - container?.isEmpty ?? true - } -} diff --git a/Sources/BinaryCodable/Encoding/UnkeyedEncoder.swift b/Sources/BinaryCodable/Encoding/UnkeyedEncoder.swift index bb8398a..8b4289c 100644 --- a/Sources/BinaryCodable/Encoding/UnkeyedEncoder.swift +++ b/Sources/BinaryCodable/Encoding/UnkeyedEncoder.swift @@ -1,91 +1,61 @@ import Foundation final class UnkeyedEncoder: AbstractEncodingNode, UnkeyedEncodingContainer { - + + private var encodedValues: [EncodableContainer] = [] + var count: Int { - content.count + nilIndices.count + encodedValues.count + } + + func encodeNil() throws { + encodedValues.append(NilContainer()) } - - private var content = [EncodingContainer]() - - private var nilIndices = Set() - + @discardableResult - private func assign(_ encoded: () throws -> T) rethrows -> T where T: EncodingContainer { - let value = try encoded() - content.append(value) + private func add(_ value: T) -> T where T: EncodableContainer { + encodedValues.append(value) return value } - - func encodeNil() throws { - nilIndices.insert(count) - } - - func encode(_ value: T) throws where T : Encodable { - if value is AnyOptional { - try assign { - try EncodingNode(path: codingPath, info: userInfo, optional: true).encoding(value) - } - } else if let primitive = value as? EncodablePrimitive { - try assign { - try wrapError(path: codingPath) { - try EncodedPrimitive(primitive: primitive) - } - } - } else { - let node = try EncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - assign { node } - } + + private func addedNode() -> EncodingNode { + let node = EncodingNode(needsLengthData: true, codingPath: codingPath, userInfo: userInfo) + return add(node) } - + func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = assign { - KeyedEncoder(path: codingPath, info: userInfo, optional: false) - } - return KeyedEncodingContainer(container) + KeyedEncodingContainer(add(KeyedEncoder(needsLengthData: true, codingPath: codingPath, userInfo: userInfo))) } - + func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - assign { - UnkeyedEncoder(path: codingPath, info: userInfo, optional: false) - } + add(UnkeyedEncoder(needsLengthData: true, codingPath: codingPath, userInfo: userInfo)) } - + func superEncoder() -> Encoder { - assign { - EncodingNode(path: codingPath, info: userInfo, optional: false) - } + addedNode() + } + + func encode(_ value: T) throws where T : Encodable { + let encoded = try encodeValue(value, needsLengthData: true) + add(encoded) } + } -extension UnkeyedEncoder: EncodingContainer { +extension UnkeyedEncoder: EncodableContainer { - private var rawIndicesData: Data { - nilIndices.sorted().map { $0.variableLengthEncoding }.joinedData - } - - private var nilIndicesData: Data { - let count = nilIndices.count - return count.variableLengthEncoding + rawIndicesData - } - - private var contentData: Data { - content.map { $0.dataWithLengthInformationIfRequired }.joinedData + var needsNilIndicator: Bool { + false } - var data: Data { - if prependNilIndexSetForUnkeyedContainers { - return nilIndicesData + contentData - } else { - return contentData - } - } - - var dataType: DataType { - .variableLength + var isNil: Bool { + false } - var isEmpty: Bool { - count == 0 + func containedData() throws -> Data { + try encodedValues.map { + let data = try $0.completeData() + return data + }.joinedData } } diff --git a/Sources/BinaryCodable/Encoding/ValueEncoder.swift b/Sources/BinaryCodable/Encoding/ValueEncoder.swift index 94a6ab6..ba9f9a7 100644 --- a/Sources/BinaryCodable/Encoding/ValueEncoder.swift +++ b/Sources/BinaryCodable/Encoding/ValueEncoder.swift @@ -1,104 +1,41 @@ import Foundation final class ValueEncoder: AbstractEncodingNode, SingleValueEncodingContainer { - - private var container: EncodingContainer? - private var containerHasOptionalContent: Bool = false - private var hasValue = false - - func encodeNil() throws { - assign { - containsOptional = true - return nil - } + private var encodedValue: EncodableContainer? + + init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { + super.init(needsLengthData: false, codingPath: codingPath, userInfo: userInfo) } - - private func assign(_ encoded: () throws -> EncodingContainer?) rethrows { - guard !hasValue else { - fatalError("Attempt to encode multiple values in single value container") + + func encodeNil() throws { + guard encodedValue == nil else { + throw EncodingError.invalidValue(0, .init(codingPath: codingPath, debugDescription: "Single value container: Multiple calls to encodeNil() or encode()")) } - container = try encoded() - hasValue = true + encodedValue = NilContainer() } - + func encode(_ value: T) throws where T : Encodable { - if value is AnyOptional { - try assign { - containerHasOptionalContent = true - return try EncodingNode(path: codingPath, info: userInfo, optional: true).encoding(value) - } - } else if let primitive = value as? EncodablePrimitive { - // Note: This assignment also work for optionals with a value, so - // we need to check for optionals explicitly before - try assign { - try wrapError(path: codingPath) { - try EncodedPrimitive(primitive: primitive) - } - } - } else { - try assign { - try EncodingNode(path: codingPath, info: userInfo, optional: false).encoding(value) - } + guard encodedValue == nil else { + throw EncodingError.invalidValue(value, .init(codingPath: codingPath, debugDescription: "Single value container: Multiple calls to encodeNil() or encode()")) } + self.encodedValue = try encodeValue(value, needsLengthData: false) } } -extension ValueEncoder: EncodingContainer { - - private var isNil: Bool { container == nil } - - var data: Data { - if containsOptional { - guard let container else { - return Data([0]) - } - return Data([1]) + container.dataWithLengthInformationIfRequired - } - return container?.data ?? .empty - } - - var dataWithLengthInformationIfRequired: Data { - if containsOptional { - return data - } - guard dataType == .variableLength else { - return data - } - return dataWithLengthInformation - } - - var dataType: DataType { - if containsOptional { - return .variableLength - } - return container?.dataType ?? .byte - } +extension ValueEncoder: EncodableContainer { - var isEmpty: Bool { - container?.isEmpty ?? true - } + var needsNilIndicator: Bool { true } - private var optionalDataWithinKeyedContainer: Data { - guard let container else { - return Data([0]) - } - guard !containerHasOptionalContent else { - return container.data - } - return Data([1]) + container.dataWithLengthInformationIfRequired + var isNil: Bool { + encodedValue is NilContainer } - func encodeWithKey(_ key: CodingKeyWrapper) -> Data { - if containsOptional || containerHasOptionalContent { - guard !isNil else { - // Nothing to do, nil is ommited for keyed containers - return Data() - } - let data = optionalDataWithinKeyedContainer - return key.encode(for: .variableLength) + data.count.variableLengthEncoding + data - } else { - return key.encode(for: container?.dataType ?? .byte) + (container?.dataWithLengthInformationIfRequired ?? .empty) + func containedData() throws -> Data { + guard let encodedValue else { + throw EncodingError.invalidValue(0, .init(codingPath: codingPath, debugDescription: "No value or nil encoded in single value container")) } + let data = try encodedValue.completeData() + return data } } diff --git a/Sources/BinaryCodable/ErrorHelper.swift b/Sources/BinaryCodable/ErrorHelper.swift deleted file mode 100644 index 83e52d2..0000000 --- a/Sources/BinaryCodable/ErrorHelper.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -func wrapError(path: [CodingKey], _ block: () throws -> T) rethrows -> T { - do { - return try block() - } catch let error as EncodingError { - switch error { - case .invalidValue(let any, let context): - let newContext = EncodingError.Context( - codingPath: path + context.codingPath, - debugDescription: context.debugDescription, - underlyingError: context.underlyingError) - throw EncodingError.invalidValue(any, newContext) - @unknown default: - throw error - } - } catch let error { - let context = EncodingError.Context(codingPath: path, debugDescription: "An unknown error occured", underlyingError: error) - throw EncodingError.invalidValue("", context) - } -} diff --git a/Sources/BinaryCodable/Extensions/Data+Extensions.swift b/Sources/BinaryCodable/Extensions/Data+Extensions.swift index ef32019..25c090b 100644 --- a/Sources/BinaryCodable/Extensions/Data+Extensions.swift +++ b/Sources/BinaryCodable/Extensions/Data+Extensions.swift @@ -25,15 +25,36 @@ extension Sequence where Element: Sequence, Element.Element == UInt8 { } } -func read(data: Data, into value: T) -> T { - Data(data).withUnsafeBytes { - $0.baseAddress!.load(as: T.self) +extension Data { + + /** + Interpret the binary data as another type. + - Parameter type: The type to interpret + */ + func interpreted(as type: T.Type = T.self) -> T { + Data(self).withUnsafeBytes { + $0.baseAddress!.load(as: T.self) + } + } + + /** + Extract the binary representation of a value. + - Parameter value: The value to convert to binary data + */ + init(underlying value: T) { + var target = value + self = Swift.withUnsafeBytes(of: &target) { + Data($0) + } } } -func toData(_ value: T) -> Data { - var target = value - return withUnsafeBytes(of: &target) { - Data($0) +extension Optional { + + var view: String { + guard let self else { + return "nil" + } + return "\(Array(self))" } } diff --git a/Sources/BinaryCodable/Extensions/DecodingError+Extensions.swift b/Sources/BinaryCodable/Extensions/DecodingError+Extensions.swift index ccb7983..6635350 100644 --- a/Sources/BinaryCodable/Extensions/DecodingError+Extensions.swift +++ b/Sources/BinaryCodable/Extensions/DecodingError+Extensions.swift @@ -2,25 +2,27 @@ import Foundation extension DecodingError { - static func variableLengthEncodedIntegerOutOfRange(_ path: [CodingKey]) -> DecodingError { - corruptedError(path, description: "Encoded variable-length integer out of range") + static func variableLengthEncodedIntegerOutOfRange(_ codingPath: [CodingKey]) -> DecodingError { + corrupted("Encoded variable-length integer out of range", codingPath: codingPath) } - static func invalidDataSize(_ path: [CodingKey]) -> DecodingError { - corruptedError(path, description: "Invalid data size") + static func notFound(_ key: CodingKey, codingPath: [CodingKey], _ message: String) -> DecodingError { + .keyNotFound(key, .init(codingPath: codingPath, debugDescription: message)) } - static func multipleValuesForKey(_ path: [CodingKey], _ key: DecodingKey) -> DecodingError { - corruptedError(path, description: "Multiple values for key \(key)") + static func valueNotFound(_ type: Any.Type, codingPath: [CodingKey], _ message: String) -> DecodingError { + .valueNotFound(type, .init(codingPath: codingPath, debugDescription: message)) } - static func prematureEndOfData(_ path: [CodingKey]) -> DecodingError { - corruptedError(path, description: "Premature end of data") + static func corrupted(_ message: String, codingPath: [CodingKey]) -> DecodingError { + return .dataCorrupted(.init(codingPath: codingPath, debugDescription: message)) } - private static func corruptedError(_ path: [CodingKey], description: String) -> DecodingError { - let context = DecodingError.Context(codingPath: path, debugDescription: description) - return .dataCorrupted(context) + static func invalidSize(size: Int, for type: String, codingPath: [CodingKey]) -> DecodingError { + .dataCorrupted(.init(codingPath: codingPath, debugDescription: "Invalid size \(size) for type \(type)")) } + static func prematureEndOfData(_ codingPath: [CodingKey]) -> DecodingError { + corrupted("Premature end of data", codingPath: codingPath) + } } diff --git a/Sources/BinaryCodable/Primitives/Bool+Coding.swift b/Sources/BinaryCodable/Primitives/Bool+Coding.swift index 5d318e1..e17ba67 100644 --- a/Sources/BinaryCodable/Primitives/Bool+Coding.swift +++ b/Sources/BinaryCodable/Primitives/Bool+Coding.swift @@ -1,29 +1,27 @@ import Foundation extension Bool: EncodablePrimitive { - - static var dataType: DataType { - .variableLengthInteger - } - - func data() -> Data { + + var encodedData: Data { Data([self ? 1 : 0]) } } extension Bool: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { + init(data: Data, codingPath: [CodingKey]) throws { guard data.count == 1 else { - throw DecodingError.invalidDataSize(path) + throw DecodingError.invalidSize(size: data.count, for: "Bool", codingPath: codingPath) + } + let byte = data[data.startIndex] + switch byte { + case 0: + self = false + case 1: + self = true + default: + throw DecodingError.corrupted("Found value \(byte) while decoding boolean", codingPath: codingPath) } self = data[data.startIndex] > 0 } } - -extension Bool: ProtobufCodable { - - var protoType: String { "bool" } - - static var zero: Bool { false } -} diff --git a/Sources/BinaryCodable/Primitives/Data+Coding.swift b/Sources/BinaryCodable/Primitives/Data+Coding.swift index 3035a89..63e58c8 100644 --- a/Sources/BinaryCodable/Primitives/Data+Coding.swift +++ b/Sources/BinaryCodable/Primitives/Data+Coding.swift @@ -2,25 +2,14 @@ import Foundation extension Data: EncodablePrimitive { - static var dataType: DataType { - .variableLength - } - - func data() -> Data { + var encodedData: Data { self } } extension Data: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) { - self = Data(data) + init(data: Data, codingPath: [CodingKey]) { + self = data } } - -extension Data: ProtobufCodable { - - var protoType: String { "bytes" } - - static var zero: Data { .empty } -} diff --git a/Sources/BinaryCodable/Primitives/Double+Coding.swift b/Sources/BinaryCodable/Primitives/Double+Coding.swift index 02057f2..d99353e 100644 --- a/Sources/BinaryCodable/Primitives/Double+Coding.swift +++ b/Sources/BinaryCodable/Primitives/Double+Coding.swift @@ -1,39 +1,19 @@ import Foundation extension Double: EncodablePrimitive { - - func data() -> Data { - toData(bitPattern.bigEndian) - } - - static var dataType: DataType { - .eightBytes + + var encodedData: Data { + .init(underlying: bitPattern.bigEndian) } } extension Double: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { + init(data: Data, codingPath: [CodingKey]) throws { guard data.count == MemoryLayout.size else { - throw DecodingError.invalidDataSize(path) + throw DecodingError.invalidSize(size: data.count, for: "Double", codingPath: codingPath) } - let value = UInt64(bigEndian: read(data: data, into: UInt64.zero)) + let value = UInt64(bigEndian: data.interpreted()) self.init(bitPattern: value) } } - -extension Double: ProtobufEncodable { - - func protobufData() throws -> Data { - data().swapped - } - - var protoType: String { "double" } -} - -extension Double: ProtobufDecodable { - - init(fromProtobuf data: Data, path: [CodingKey]) throws { - try self.init(decodeFrom: data.swapped, path: path) - } -} diff --git a/Sources/BinaryCodable/Primitives/Float+Coding.swift b/Sources/BinaryCodable/Primitives/Float+Coding.swift index 5cc9cfc..1948d27 100644 --- a/Sources/BinaryCodable/Primitives/Float+Coding.swift +++ b/Sources/BinaryCodable/Primitives/Float+Coding.swift @@ -2,35 +2,19 @@ import Foundation extension Float: EncodablePrimitive { - func data() -> Data { - toData(bitPattern.bigEndian) - } - - static var dataType: DataType { - .fourBytes + var encodedData: Data { + .init(underlying: bitPattern.bigEndian) } } extension Float: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { + init(data: Data, codingPath: [CodingKey]) throws { guard data.count == MemoryLayout.size else { - throw DecodingError.invalidDataSize(path) + throw DecodingError.invalidSize(size: data.count, for: "Float", codingPath: codingPath) } - let value = UInt32(bigEndian: read(data: data, into: UInt32.zero)) + let value = UInt32(bigEndian: data.interpreted()) self.init(bitPattern: value) } } -extension Float: ProtobufCodable { - - func protobufData() throws -> Data { - data().swapped - } - - init(fromProtobuf data: Data, path: [CodingKey]) throws { - try self.init(decodeFrom: data.swapped, path: path) - } - - var protoType: String { "float" } -} diff --git a/Sources/BinaryCodable/Primitives/Int+Coding.swift b/Sources/BinaryCodable/Primitives/Int+Coding.swift index feaa770..ba26e80 100644 --- a/Sources/BinaryCodable/Primitives/Int+Coding.swift +++ b/Sources/BinaryCodable/Primitives/Int+Coding.swift @@ -1,104 +1,53 @@ import Foundation extension Int: EncodablePrimitive { - - func data() -> Data { - zigZagEncoded - } - - static var dataType: DataType { - .variableLengthInteger - } + + /// The integer encoded using zig-zag variable length encoding + var encodedData: Data { zigZagEncoded } } extension Int: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { - try self.init(fromZigZag: data, path: path) + init(data: Data, codingPath: [CodingKey]) throws { + try self.init(fromZigZag: data, codingPath: codingPath) } } -extension Int: VariableLengthCodable { - - var variableLengthEncoding: Data { - Int64(self).variableLengthEncoding - } - - init(fromVarint data: Data, path: [CodingKey]) throws { - let intValue = try Int64(fromVarint: data, path: path) - guard let value = Int(exactly: intValue) else { - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) - } - self = value - } -} - -extension Int: FixedSizeCompatible { +extension Int: ZigZagEncodable { - public static var fixedSizeDataType: DataType { - .eightBytes + var zigZagEncoded: Data { + Int64(self).zigZagEncoded } +} - public var fixedProtoType: String { - "sfixed64" - } +extension Int: ZigZagDecodable { - public init(fromFixedSize data: Data, path: [CodingKey]) throws { - let signed = try Int64(fromFixedSize: data, path: path) - guard let value = Int(exactly: signed) else { - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) + init(fromZigZag data: Data, codingPath: [any CodingKey]) throws { + let raw = try Int64(data: data, codingPath: codingPath) + guard let value = Int(exactly: raw) else { + throw DecodingError.corrupted("Decoded value \(raw) is out of range for type Int", codingPath: codingPath) } self = value } - - public var fixedSizeEncoded: Data { - Int64(self).fixedSizeEncoded - } } -extension Int: SignedValueCompatible { - public var positiveProtoType: String { - "int64" +extension Int: VariableLengthEncodable { + + /// The value encoded using variable length encoding + var variableLengthEncoding: Data { + Int64(self).variableLengthEncoding } } -extension Int: ZigZagCodable { - - /** - Encode a 64 bit signed integer using variable-length encoding. - - The sign of the value is extracted and appended as an additional bit. - Positive signed values are thus encoded as `UInt(value) * 2`, and negative values as `UInt(abs(value) * 2 + 1` +extension Int: VariableLengthDecodable { - - Parameter value: The value to encode. - - Returns: The value encoded as binary data (1 to 9 byte) - */ - var zigZagEncoded: Data { - Int64(self).zigZagEncoded - } - - init(fromZigZag data: Data, path: [CodingKey]) throws { - let raw = try Int64(fromZigZag: data, path: path) - guard let value = Int(exactly: raw) else { - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) + init(fromVarint data: Data, codingPath: [CodingKey]) throws { + let intValue = try Int64(fromVarint: data, codingPath: codingPath) + guard let value = Int(exactly: intValue) else { + throw DecodingError.variableLengthEncodedIntegerOutOfRange(codingPath) } self = value } } -extension Int: ProtobufEncodable { - - func protobufData() -> Data { - variableLengthEncoding - } - - var protoType: String { "sint64" } -} - -extension Int: ProtobufDecodable { - - init(fromProtobuf data: Data, path: [CodingKey]) throws { - try self.init(fromVarint: data, path: path) - } -} diff --git a/Sources/BinaryCodable/Primitives/Int16+Coding.swift b/Sources/BinaryCodable/Primitives/Int16+Coding.swift index 18a67de..70a107d 100644 --- a/Sources/BinaryCodable/Primitives/Int16+Coding.swift +++ b/Sources/BinaryCodable/Primitives/Int16+Coding.swift @@ -1,23 +1,19 @@ import Foundation extension Int16: EncodablePrimitive { - - func data() -> Data { - toData(UInt16(bitPattern: self).littleEndian) - } - - static var dataType: DataType { - .twoBytes + + var encodedData: Data { + .init(underlying: UInt16(bitPattern: self).littleEndian) } } extension Int16: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { + init(data: Data, codingPath: [CodingKey]) throws { guard data.count == MemoryLayout.size else { - throw DecodingError.invalidDataSize(path) + throw DecodingError.invalidSize(size: data.count, for: "Int16", codingPath: codingPath) } - let value = UInt16(littleEndian: read(data: data, into: UInt16.zero)) + let value = UInt16(littleEndian: data.interpreted()) self.init(bitPattern: value) } } diff --git a/Sources/BinaryCodable/Primitives/Int32+Coding.swift b/Sources/BinaryCodable/Primitives/Int32+Coding.swift index a5ade4d..e52a2d5 100644 --- a/Sources/BinaryCodable/Primitives/Int32+Coding.swift +++ b/Sources/BinaryCodable/Primitives/Int32+Coding.swift @@ -1,91 +1,48 @@ import Foundation extension Int32: EncodablePrimitive { - - func data() -> Data { - zigZagEncoded - } - - static var dataType: DataType { - .variableLengthInteger - } + + /// The integer encoded using zig-zag variable length encoding + var encodedData: Data { zigZagEncoded } } extension Int32: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { - try self.init(fromZigZag: data, path: path) + init(data: Data, codingPath: [CodingKey]) throws { + try self.init(fromZigZag: data, codingPath: codingPath) } } -extension Int32: ZigZagCodable { +extension Int32: ZigZagEncodable { - /** - Encode a 64 bit signed integer using variable-length encoding. - - The sign of the value is extracted and appended as an additional bit. - Positive signed values are thus encoded as `UInt(value) * 2`, and negative values as `UInt(abs(value) * 2 + 1` - - - Parameter value: The value to encode. - - Returns: The value encoded as binary data (1 to 9 byte) - */ var zigZagEncoded: Data { Int64(self).zigZagEncoded } - - init(fromZigZag data: Data, path: [CodingKey]) throws { - let raw = try Int64(fromZigZag: data, path: path) - guard let value = Int32(exactly: raw) else { - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) - } - self = value - } } -extension Int32: FixedSizeCompatible { - - public static var fixedSizeDataType: DataType { - .fourBytes - } +extension Int32: ZigZagDecodable { - public var fixedProtoType: String { - "sfixed32" - } - - public init(fromFixedSize data: Data, path: [CodingKey]) throws { - guard data.count == MemoryLayout.size else { - throw DecodingError.invalidDataSize(path) + init(fromZigZag data: Data, codingPath: [any CodingKey]) throws { + let raw = try Int64(data: data, codingPath: codingPath) + guard let value = Int32(exactly: raw) else { + throw DecodingError.corrupted("Decoded value \(raw) is out of range for type Int32", codingPath: codingPath) } - let value = UInt32(littleEndian: read(data: data, into: UInt32.zero)) - self.init(bitPattern: value) - } - - public var fixedSizeEncoded: Data { - let value = UInt32(bitPattern: littleEndian) - return toData(value) + self = value } } -extension Int32: SignedValueCompatible { +extension Int32: VariableLengthEncodable { - public var positiveProtoType: String { - "int32" + /// The value encoded using variable length encoding + var variableLengthEncoding: Data { + UInt32(bitPattern: self).variableLengthEncoding } } -extension Int32: ProtobufCodable { +extension Int32: VariableLengthDecodable { - func protobufData() -> Data { - Int64(self).protobufData() + init(fromVarint data: Data, codingPath: [CodingKey]) throws { + let value = try UInt32(data: data, codingPath: codingPath) + self = Int32(bitPattern: value) } - - init(fromProtobuf data: Data, path: [CodingKey]) throws { - let intValue = try Int64(fromProtobuf: data, path: path) - guard let value = Int32(exactly: intValue) else { - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) - } - self = value - } - - var protoType: String { "sint32" } } diff --git a/Sources/BinaryCodable/Primitives/Int64+Coding.swift b/Sources/BinaryCodable/Primitives/Int64+Coding.swift index 269a9cb..02b2bca 100644 --- a/Sources/BinaryCodable/Primitives/Int64+Coding.swift +++ b/Sources/BinaryCodable/Primitives/Int64+Coding.swift @@ -1,46 +1,20 @@ import Foundation extension Int64: EncodablePrimitive { - - func data() -> Data { - zigZagEncoded - } - - static var dataType: DataType { - .variableLengthInteger - } + + /// The value encoded using zig-zag variable length encoding + var encodedData: Data { zigZagEncoded } } extension Int64: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { - try self.init(fromZigZag: data, path: path) - } -} - -extension Int64: VariableLengthCodable { - - var variableLengthEncoding: Data { - UInt64(bitPattern: self).variableLengthEncoding - } - - init(fromVarint data: Data, path: [CodingKey]) throws { - let value = try UInt64(fromVarint: data, path: path) - self = Int64(bitPattern: value) + init(data: Data, codingPath: [CodingKey]) throws { + try self.init(fromZigZag: data, codingPath: codingPath) } } extension Int64: ZigZagEncodable { - - /** - Encode a 64 bit signed integer using variable-length encoding. - - The sign of the value is extracted and appended as an additional bit. - Positive signed values are thus encoded as `UInt(value) * 2`, and negative values as `UInt(abs(value) * 2 + 1` - - - Parameter value: The value to encode. - - Returns: The value encoded as binary data (1 to 9 byte) - */ + var zigZagEncoded: Data { guard self < 0 else { return (UInt64(self.magnitude) << 1).variableLengthEncoding @@ -50,10 +24,10 @@ extension Int64: ZigZagEncodable { } extension Int64: ZigZagDecodable { - - init(fromZigZag data: Data, path: [CodingKey]) throws { - let unsigned = try UInt64(fromVarint: data, path: path) - + + init(fromZigZag data: Data, codingPath: [CodingKey]) throws { + let unsigned = try UInt64(data: data, codingPath: codingPath) + // Check the last bit to get sign if unsigned & 1 > 0 { // Divide by 2 and subtract one to get absolute value of negative values. @@ -65,50 +39,19 @@ extension Int64: ZigZagDecodable { } } -extension Int64: FixedSizeCompatible { - - static public var fixedSizeDataType: DataType { - .eightBytes - } - - public var fixedProtoType: String { - "sfixed64" - } - - public init(fromFixedSize data: Data, path: [CodingKey]) throws { - guard data.count == MemoryLayout.size else { - throw DecodingError.invalidDataSize(path) - } - let value = UInt64(littleEndian: read(data: data, into: UInt64.zero)) - self.init(bitPattern: value) - } - - public var fixedSizeEncoded: Data { - let value = UInt64(bitPattern: littleEndian) - return toData(value) - } -} - -extension Int64: SignedValueCompatible { +extension Int64: VariableLengthEncodable { - public var positiveProtoType: String { - "int64" - } -} - -extension Int64: ProtobufEncodable { - - func protobufData() -> Data { - variableLengthEncoding + /// The value encoded using variable length encoding + var variableLengthEncoding: Data { + UInt64(bitPattern: self).encodedData } - var protoType: String { "sint64" } } -extension Int64: ProtobufDecodable { +extension Int64: VariableLengthDecodable { - init(fromProtobuf data: Data, path: [CodingKey]) throws { - try self.init(fromVarint: data, path: path) + init(fromVarint data: Data, codingPath: [CodingKey]) throws { + let value = try UInt64(data: data, codingPath: codingPath) + self = Int64(bitPattern: value) } - } diff --git a/Sources/BinaryCodable/Primitives/Int8+Coding.swift b/Sources/BinaryCodable/Primitives/Int8+Coding.swift index f5f971e..e30c156 100644 --- a/Sources/BinaryCodable/Primitives/Int8+Coding.swift +++ b/Sources/BinaryCodable/Primitives/Int8+Coding.swift @@ -1,21 +1,17 @@ import Foundation extension Int8: EncodablePrimitive { - - func data() -> Data { + + var encodedData: Data { Data([UInt8(bitPattern: self)]) } - - static var dataType: DataType { - .byte - } } extension Int8: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { + init(data: Data, codingPath: [CodingKey]) throws { guard data.count == 1 else { - throw DecodingError.invalidDataSize(path) + throw DecodingError.invalidSize(size: data.count, for: "Int8", codingPath: codingPath) } self.init(bitPattern: data[data.startIndex]) } diff --git a/Sources/BinaryCodable/Primitives/String+Coding.swift b/Sources/BinaryCodable/Primitives/String+Coding.swift index c15fa10..e7ed0fc 100644 --- a/Sources/BinaryCodable/Primitives/String+Coding.swift +++ b/Sources/BinaryCodable/Primitives/String+Coding.swift @@ -1,33 +1,18 @@ import Foundation extension String: EncodablePrimitive { - - static var dataType: DataType { - .variableLength - } - - func data() throws -> Data { - guard let result = data(using: .utf8) else { - throw EncodingError.invalidValue(self, .init(codingPath: [], debugDescription: "String is not UTF-8")) - } - return result + + var encodedData: Data { + data(using: .utf8)! } } extension String: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { + init(data: Data, codingPath: [CodingKey]) throws { guard let value = String(data: data, encoding: .utf8) else { - let context = DecodingError.Context(codingPath: path, debugDescription: "Invalid string") - throw DecodingError.dataCorrupted(context) + throw DecodingError.corrupted("Invalid string", codingPath: codingPath) } self = value } } - -extension String: ProtobufCodable { - - var protoType: String { "string" } - - static let zero = "" -} diff --git a/Sources/BinaryCodable/Primitives/UInt+Coding.swift b/Sources/BinaryCodable/Primitives/UInt+Coding.swift index ada0a4a..fe06e6e 100644 --- a/Sources/BinaryCodable/Primitives/UInt+Coding.swift +++ b/Sources/BinaryCodable/Primitives/UInt+Coding.swift @@ -1,74 +1,29 @@ import Foundation extension UInt: EncodablePrimitive { - - func data() -> Data { - variableLengthEncoding - } - - static var dataType: DataType { - .variableLengthInteger - } -} -extension UInt: DecodablePrimitive { - - init(decodeFrom data: Data, path: [CodingKey]) throws { - try self.init(fromVarint: data, path: path) - } + var encodedData: Data { variableLengthEncoding } } -extension UInt: FixedSizeCompatible { - - static public var fixedSizeDataType: DataType { - .eightBytes - } - - public var fixedProtoType: String { - "fixed64" - } +extension UInt: DecodablePrimitive { - public init(fromFixedSize data: Data, path: [CodingKey]) throws { - let intValue = try UInt64(fromFixedSize: data, path: path) - guard let value = UInt(exactly: intValue) else { - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) - } - self = value - } + init(data: Data, codingPath: [CodingKey]) throws { + try self.init(fromVarint: data, codingPath: codingPath) - public var fixedSizeEncoded: Data { - UInt64(self).fixedSizeEncoded } } extension UInt: VariableLengthCodable { - + var variableLengthEncoding: Data { UInt64(self).variableLengthEncoding } - - init(fromVarint data: Data, path: [CodingKey]) throws { - let intValue = try UInt64(fromVarint: data, path: path) - guard let value = UInt(exactly: intValue) else { - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) + + init(fromVarint data: Data, codingPath: [any CodingKey]) throws { + let raw = try UInt64(fromVarint: data, codingPath: codingPath) + guard let value = UInt(exactly: raw) else { + throw DecodingError.corrupted("Decoded value \(raw) is out of range for type UInt", codingPath: codingPath) } self = value } } - -extension UInt: ProtobufEncodable { - - func protobufData() -> Data { - variableLengthEncoding - } - - var protoType: String { "uint64" } -} - -extension UInt: ProtobufDecodable { - - init(fromProtobuf data: Data, path: [CodingKey]) throws { - try self.init(fromVarint: data, path: path) - } - -} diff --git a/Sources/BinaryCodable/Primitives/UInt16+Coding.swift b/Sources/BinaryCodable/Primitives/UInt16+Coding.swift index a0b7204..c087442 100644 --- a/Sources/BinaryCodable/Primitives/UInt16+Coding.swift +++ b/Sources/BinaryCodable/Primitives/UInt16+Coding.swift @@ -1,22 +1,18 @@ import Foundation extension UInt16: EncodablePrimitive { - - func data() -> Data { - toData(littleEndian) - } - - static var dataType: DataType { - .twoBytes + + var encodedData: Data { + .init(underlying: littleEndian) } } extension UInt16: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { + init(data: Data, codingPath: [CodingKey]) throws { guard data.count == MemoryLayout.size else { - throw DecodingError.invalidDataSize(path) + throw DecodingError.invalidSize(size: data.count, for: "UInt16", codingPath: codingPath) } - self.init(littleEndian: read(data: data, into: UInt16.zero)) + self.init(littleEndian: data.interpreted()) } } diff --git a/Sources/BinaryCodable/Primitives/UInt32+Coding.swift b/Sources/BinaryCodable/Primitives/UInt32+Coding.swift index 799875f..7f75187 100644 --- a/Sources/BinaryCodable/Primitives/UInt32+Coding.swift +++ b/Sources/BinaryCodable/Primitives/UInt32+Coding.swift @@ -1,75 +1,33 @@ import Foundation extension UInt32: EncodablePrimitive { - - func data() -> Data { - variableLengthEncoding - } - - static var dataType: DataType { - .variableLengthInteger - } + + /// The value encoded using variable-length encoding + var encodedData: Data { variableLengthEncoding } } extension UInt32: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { - try self.init(fromVarint: data, path: path) + init(data: Data, codingPath: [CodingKey]) throws { + try self.init(fromVarint: data, codingPath: codingPath) } } -extension UInt32: VariableLengthCodable { - +extension UInt32: VariableLengthEncodable { + + /// The value encoded using variable-length encoding var variableLengthEncoding: Data { UInt64(self).variableLengthEncoding } - - init(fromVarint data: Data, path: [CodingKey]) throws { - let intValue = try UInt64(fromVarint: data, path: path) - guard let value = UInt32(exactly: intValue) else { - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) - } - self = value - } -} - -extension UInt32: FixedSizeCompatible { - - static public var fixedSizeDataType: DataType { - .fourBytes - } - - public var fixedProtoType: String { - "fixed32" - } - - public init(fromFixedSize data: Data, path: [CodingKey]) throws { - guard data.count == MemoryLayout.size else { - throw DecodingError.invalidDataSize(path) - } - self.init(littleEndian: read(data: data, into: UInt32.zero)) - } - - public var fixedSizeEncoded: Data { - toData(littleEndian) - } -} - -extension UInt32: ProtobufEncodable { - - func protobufData() -> Data { - UInt64(self).protobufData() - } - var protoType: String { "uint32" } } -extension UInt32: ProtobufDecodable { +extension UInt32: VariableLengthDecodable { - init(fromProtobuf data: Data, path: [CodingKey]) throws { - let intValue = try UInt64.init(fromProtobuf: data, path: path) - guard let value = UInt32(exactly: intValue) else { - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) + init(fromVarint data: Data, codingPath: [any CodingKey]) throws { + let raw = try UInt64(fromVarint: data, codingPath: codingPath) + guard let value = UInt32(exactly: raw) else { + throw DecodingError.corrupted("Decoded value \(raw) is out of range for type UInt32", codingPath: codingPath) } self = value } diff --git a/Sources/BinaryCodable/Primitives/UInt64+Coding.swift b/Sources/BinaryCodable/Primitives/UInt64+Coding.swift index b776d3a..71de901 100644 --- a/Sources/BinaryCodable/Primitives/UInt64+Coding.swift +++ b/Sources/BinaryCodable/Primitives/UInt64+Coding.swift @@ -1,36 +1,21 @@ import Foundation extension UInt64: EncodablePrimitive { - - func data() -> Data { - variableLengthEncoding - } - - static var dataType: DataType { - .variableLengthInteger - } + + /// The value encoded using variable-length encoding + var encodedData: Data { variableLengthEncoding } } extension UInt64: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { - try self.init(fromVarint: data, path: path) + init(data: Data, codingPath: [CodingKey]) throws { + try self.init(fromVarint: data, codingPath: codingPath) } } -extension UInt64: VariableLengthCodable { - - /** - Encode a 64 bit unsigned integer using variable-length encoding. - - The first bit in each byte is used to indicate that another byte will follow it. - So values from 0 to 2^7 - 1 (i.e. 127) will be encoded in a single byte. - In general, `n` bytes are needed to encode values from ` 2^(n-1) ` to ` 2^n - 1` - The maximum encodable value ` 2^64 - 1 ` is encoded as 9 byte. - - - Parameter value: The value to encode. - - Returns: The value encoded as binary data (1 to 9 byte) - */ +extension UInt64: VariableLengthEncodable { + + /// The value encoded using variable-length encoding var variableLengthEncoding: Data { var result = Data() var value = self @@ -52,142 +37,16 @@ extension UInt64: VariableLengthCodable { } return result } - - init(fromVarint data: Data, path: [CodingKey]) throws { - var result: UInt64 = 0 - - // There are always 7 usable bits per byte, for 8 bytes - for byteIndex in 0..<8 { - guard data.startIndex + byteIndex < data.endIndex else { - throw DecodingError.prematureEndOfData(path) - } - let nextByte = UInt64(data[data.startIndex + byteIndex]) - // Insert the last 7 bit of the byte at the end - result += UInt64(nextByte & 0x7F) << (byteIndex*7) - // Check if an additional byte is coming - guard nextByte & 0x80 > 0 else { - self = result - return - } - } - guard data.startIndex + 8 < data.endIndex else { - throw DecodingError.prematureEndOfData(path) - } - // The 9th byte has no next-byte bit, so all 8 bits are used - let nextByte = UInt64(data[data.startIndex + 8]) - result += UInt64(nextByte) << 56 - self = result - } } -extension UInt64: FixedSizeCompatible { - - static public var fixedSizeDataType: DataType { - .eightBytes - } +extension UInt64: VariableLengthDecodable { - public var fixedProtoType: String { - "fixed64" - } - - public init(fromFixedSize data: Data, path: [CodingKey]) throws { - guard data.count == MemoryLayout.size else { - throw DecodingError.invalidDataSize(path) + init(fromVarint data: Data, codingPath: [any CodingKey]) throws { + let storage = DecodingStorage(data: data, codingPath: codingPath) + let value = try storage.decodeUInt64() + guard storage.isAtEnd else { + throw DecodingError.corrupted("\(storage.numberOfRemainingBytes) unused bytes left after decoding variable length integer", codingPath: codingPath) } - self.init(littleEndian: read(data: data, into: UInt64.zero)) - } - - public var fixedSizeEncoded: Data { - toData(littleEndian) - } -} - -extension UInt64: ZigZagEncodable { - - /** - Encode a 64 bit unsigned integer using variable-length encoding. - - The first bit in each byte is used to indicate that another byte will follow it. - So values from 0 to 2^7 - 1 (i.e. 127) will be encoded in a single byte. - In general, `n` bytes are needed to encode values from ` 2^(n-1) ` to ` 2^n - 1 ` - The maximum encodable value ` 2^64 - 1 ` is encoded as 10 byte. - - - Parameter value: The value to encode. - - Returns: The value encoded as binary data (1 to 10 byte) - */ - var zigZagEncoded: Data { - var result = Data() - var value = self - while true { - // Extract 7 bit from value - let nextByte = UInt8(value & 0x7F) - value = value >> 7 - guard value > 0 else { - result.append(nextByte) - return result - } - // Set 8th bit to indicate another byte - result.append(nextByte | 0x80) - } - } - -} - -extension UInt64: ZigZagDecodable { - - /** - Extract a variable-length value from a container. - - Parameter byteProvider: The data container with the encoded data. - - Throws: `ProtobufDecodingError.invalidVarintEncoding` or - `ProtobufDecodingError.missingData` - - Returns: The decoded value. - */ - init(fromZigZag data: Data, path: [CodingKey]) throws { - var result: UInt64 = 0 - - for byteIndex in 0...8 { - guard data.startIndex + byteIndex < data.endIndex else { - throw DecodingError.prematureEndOfData(path) - } - let nextByte = UInt64(data[data.startIndex + byteIndex]) - // Insert the last 7 bit of the byte at the end - result += UInt64(nextByte & 0x7F) << (byteIndex*7) - // Check if an additional byte is coming - guard nextByte & 0x80 > 0 else { - self = result - return - } - } - // If we're here, the 9th byte had the MSB set - guard data.startIndex + 9 < data.endIndex else { - throw DecodingError.prematureEndOfData(path) - } - let nextByte = data[data.startIndex + 9] - switch nextByte { - case 0: - break - case 1: - result += 1 << 63 - default: - // Only 0x01 and 0x00 are valid for the 10th byte, or the UInt64 would overflow - throw DecodingError.variableLengthEncodedIntegerOutOfRange(path) - } - self = result - } -} - -extension UInt64: ProtobufEncodable { - - func protobufData() -> Data { - variableLengthEncoding - } - - var protoType: String { "uint64" } -} - -extension UInt64: ProtobufDecodable { - - init(fromProtobuf data: Data, path: [CodingKey]) throws { - try self.init(fromVarint: data, path: path) + self = value } } diff --git a/Sources/BinaryCodable/Primitives/UInt8+Coding.swift b/Sources/BinaryCodable/Primitives/UInt8+Coding.swift index 1469bf4..96a0fea 100644 --- a/Sources/BinaryCodable/Primitives/UInt8+Coding.swift +++ b/Sources/BinaryCodable/Primitives/UInt8+Coding.swift @@ -1,21 +1,17 @@ import Foundation extension UInt8: EncodablePrimitive { - - func data() -> Data { + + var encodedData: Data { Data([self]) } - - static var dataType: DataType { - .byte - } } extension UInt8: DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws { + init(data: Data, codingPath: [CodingKey]) throws { guard data.count == 1 else { - throw DecodingError.invalidDataSize(path) + throw DecodingError.invalidSize(size: data.count, for: "UInt8", codingPath: codingPath) } self = data[data.startIndex] } diff --git a/Sources/BinaryCodable/ProtobufDecoder.swift b/Sources/BinaryCodable/ProtobufDecoder.swift deleted file mode 100644 index 32bef69..0000000 --- a/Sources/BinaryCodable/ProtobufDecoder.swift +++ /dev/null @@ -1,64 +0,0 @@ -import Foundation - -/** - An encoder to convert protobuf binary data back to `Codable` objects. - - Decoding unsupported data types causes `DecodingError` or `ProtobufDecodingError` errors. - - To decode from data, instantiate a decoder and specify the type: - ``` - let decoder = BinaryDecoder() - let message = try decoder.decode(Message.self, from: data) - ``` - Alternatively, the type can be inferred from context: - ``` - func decode(data: Data) throws -> Message { - try BinaryDecoder().decode(from: data) - } - ``` - There are also convenience functions to directly decode a single instance: - ``` - let message = try BinaryDecoder.decode(Message.self, from: data) - ``` - - Note: A single decoder can be used to decode multiple messages. - */ -public final class ProtobufDecoder { - - /** - Any contextual information set by the user for decoding. - - This dictionary is passed to all containers during the decoding process. - */ - public var userInfo = [CodingUserInfoKey : Any]() - - /** - Create a new binary encoder. - - Note: A single decoder can be used to decode multiple messages. - */ - public init() { - - } - - /** - Decode a type from binary data. - - Parameter type: The type to decode. - - Parameter data: The binary data which encodes the instance - - Returns: The decoded instance - - Throws: Errors of type `DecodingError` or `ProtobufDecodingError` - */ - public func decode(_ type: T.Type = T.self, from data: Data) throws -> T where T: Decodable { - let root = ProtoDecodingNode(data: data, path: [], info: userInfo) - return try type.init(from: root) - } - - /** - Decode a single value from binary data using a default decoder. - - Parameter type: The type to decode. - - Parameter data: The binary data which encodes the instance - - Returns: The decoded instance - - Throws: Errors of type `DecodingError` or `ProtobufDecodingError` - */ - public static func decode(_ type: T.Type = T.self, from data: Data) throws -> T where T: Decodable { - try ProtobufDecoder().decode(type, from: data) - } -} diff --git a/Sources/BinaryCodable/ProtobufDecodingError.swift b/Sources/BinaryCodable/ProtobufDecodingError.swift deleted file mode 100644 index 6284a99..0000000 --- a/Sources/BinaryCodable/ProtobufDecodingError.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation - -/** - An error produced while decoding binary data. - */ -public enum ProtobufDecodingError: Error { - - case unexpectedDictionaryKey - - /** - Protocol buffers don't support inheritance, so `super` can't be encoded. - */ - case superNotSupported - - /** - The encoded type contains a basic type that is not supported. - - The associated value contains a textual description of the unsupported type. - */ - case unsupportedType(String) - - /** - A decoding feature was accessed which is not supported for protobuf encoding. - - The associated value contains a textual description of the invalid access. - */ - case invalidAccess(String) - -} - -extension ProtobufDecodingError { - - static func unsupported(type t: T.Type) -> ProtobufDecodingError { - .unsupportedType("\(type(of: t))") - } -} diff --git a/Sources/BinaryCodable/ProtobufEncoder.swift b/Sources/BinaryCodable/ProtobufEncoder.swift deleted file mode 100644 index 9b47681..0000000 --- a/Sources/BinaryCodable/ProtobufEncoder.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation - -/** - An encoder to convert `Codable` objects to protobuf binary data. - - The encoder provides only limited compatibility with Google's Protocol Buffers. - - Encoding unsupported data types causes `ProtobufEncodingError` errors. - - Construct an encoder when converting instances to binary data, and feed the message(s) into it: - - ```swift - let message: Message = ... - - let encoder = ProtobufEncoder() - let data = try encoder.encode(message) - ``` - - - Note: An ecoder can be used to encode multiple messages. - */ -public final class ProtobufEncoder { - - /** - Any contextual information set by the user for encoding. - - This dictionary is passed to all containers during the encoding process. - - Contains also keys for any custom options set for the encoder. - See `sortKeysDuringEncoding`. - */ - public var userInfo = [CodingUserInfoKey : Any]() - - /** - Create a new binary encoder. - - Note: An encoder can be used to encode multiple messages. - */ - public init() { - - } - - /** - Encode a value to binary data. - - Parameter value: The value to encode - - Returns: The encoded data - - Throws: Errors of type `EncodingError` or `ProtobufEncodingError` - */ - public func encode(_ value: T) throws -> Data where T: Encodable { - let root = ProtoEncodingNode(path: [], info: userInfo, optional: false) - try value.encode(to: root) - return root.data - } - - /** - Encode a single value to binary data using a default encoder. - - Parameter value: The value to encode - - Returns: The encoded data - - Throws: Errors of type `EncodingError` or `ProtobufEncodingError` - */ - public static func encode(_ value: T) throws -> Data where T: Encodable { - try ProtobufEncoder().encode(value) - } - - func getProtobufDefinition(_ value: T) throws -> String where T: Encodable { - let root = try ProtoNode(encoding: "\(type(of: value))", path: [], info: userInfo) - .encoding(value) - return try "syntax = \"proto3\";\n\n" + root.protobufDefinition() - } -} diff --git a/Sources/BinaryCodable/ProtobufEncodingError.swift b/Sources/BinaryCodable/ProtobufEncodingError.swift deleted file mode 100644 index f3a422e..0000000 --- a/Sources/BinaryCodable/ProtobufEncodingError.swift +++ /dev/null @@ -1,88 +0,0 @@ -import Foundation - -/** - An error thrown when encoding a value using `ProtobufEncoder`. - */ -public enum ProtobufEncodingError: Error { - - /** - A procedural error occuring during encoding. - - A custom implementation of `func encode(to encoder: Encoder) throws` tried to encode multiple values into a single value container: - - ``` - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - container.encode(...) - container.encode(...) // Invalid - } - ``` - */ - case multipleValuesInSingleValueContainer - - case noValueInSingleValueContainer - - /** - The encoded type contains optional values, which are not supported in the protocol buffer format. - */ - case nilValuesNotSupported - - /** - The encoded type contains a basic type that is not supported. - - The associated value contains a textual description of the unsupported type. - */ - case unsupportedType(String) - - /** - Protocol buffers don't support inheritance, so `super` can't be encoded. - */ - case superNotSupported - - /** - The encoded type contains properties which don't have an integer key. - - The associated value contains the string key which is missing an integer key. - */ - case missingIntegerKey(String) - - /** - All values in unkeyed containers must have the same type. - */ - case multipleTypesInUnkeyedContainer - - /** - Field numbers must be positive integers not greater than `536870911` (`2^29-1`, or `0x1FFFFFFF`) - - The associated value is the integer key that is out of range. - */ - case integerKeyOutOfRange(Int) - - /** - Multiple calls to `container<>(keyedBy:)`, `unkeyedContainer()`, or `singleValueContainer()` for an encoder. - */ - case multipleContainersAccessed - - /** - No calls to `container<>(keyedBy:)`, `unkeyedContainer()`, or `singleValueContainer()` for an encoder. - */ - case noContainersAccessed - - /** - Protobuf requires an unkeyed container as the root node - */ - case rootIsNotKeyedContainer - - /** - An unavailable encoding feature was accessed. - - The associated value contains a textual description of the unsupported access. - */ - case invalidAccess(String) - - case protobufDefinitionUnavailable(String) - - static func unsupported(type value: EncodablePrimitive) -> ProtobufEncodingError { - .unsupportedType("\(type(of: value))") - } -} diff --git a/Sources/BinaryCodable/Protocols/AnyDictionary.swift b/Sources/BinaryCodable/Protocols/AnyDictionary.swift deleted file mode 100644 index ee9b819..0000000 --- a/Sources/BinaryCodable/Protocols/AnyDictionary.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation - -/** - An abstract Dictionary, without any `self` requirements. - - This protocol is only adopted by `Dictionary`, and used to correctly encode and decode dictionaries for Protobuf-compatible encoding. - */ -protocol AnyDictionary { - -} - -extension Dictionary: AnyDictionary { - -} diff --git a/Sources/BinaryCodable/Protocols/AnyOptional.swift b/Sources/BinaryCodable/Protocols/AnyOptional.swift deleted file mode 100644 index e4bf804..0000000 --- a/Sources/BinaryCodable/Protocols/AnyOptional.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation - -protocol AnyOptional { - - var isNil: Bool { get } - - static var nilValue: Self { get } -} - -extension Optional: AnyOptional { - - var isNil: Bool { - switch self { - case .none: - return true - default: - return false - } - } - - static var nilValue: Self { - return Self.none - } - -} diff --git a/Sources/BinaryCodable/Protocols/DataTypeProvider.swift b/Sources/BinaryCodable/Protocols/DataTypeProvider.swift deleted file mode 100644 index 3311940..0000000 --- a/Sources/BinaryCodable/Protocols/DataTypeProvider.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -protocol DataTypeProvider { - - static var dataType: DataType { get } -} - -extension DataTypeProvider { - - var dataType: DataType { - Self.dataType - } -} diff --git a/Sources/BinaryCodable/Protocols/DecodablePrimitive.swift b/Sources/BinaryCodable/Protocols/DecodablePrimitive.swift index ab508be..b6c7464 100644 --- a/Sources/BinaryCodable/Protocols/DecodablePrimitive.swift +++ b/Sources/BinaryCodable/Protocols/DecodablePrimitive.swift @@ -1,6 +1,13 @@ import Foundation -protocol DecodablePrimitive: DataTypeProvider { +/** + A protocol adopted by primitive types for decoding. + */ +protocol DecodablePrimitive { - init(decodeFrom data: Data, path: [CodingKey]) throws + /** + Decode a value from the data. + - Note: All provided data can be used + */ + init(data: Data, codingPath: [CodingKey]) throws } diff --git a/Sources/BinaryCodable/Protocols/EncodablePrimitive.swift b/Sources/BinaryCodable/Protocols/EncodablePrimitive.swift index e9e2f0b..0ae32f4 100644 --- a/Sources/BinaryCodable/Protocols/EncodablePrimitive.swift +++ b/Sources/BinaryCodable/Protocols/EncodablePrimitive.swift @@ -1,6 +1,13 @@ import Foundation -protocol EncodablePrimitive: DataTypeProvider { - - func data() throws -> Data +/** + A protocol adopted by all base types (Int, Data, String, ...) to provide the encoded data. + */ +protocol EncodablePrimitive { + + /** + The raw data of the encoded base type value. + - Note: No length information must be included + */ + var encodedData: Data { get } } diff --git a/Sources/BinaryCodable/Protocols/ProtobufCodable.swift b/Sources/BinaryCodable/Protocols/ProtobufCodable.swift deleted file mode 100644 index 5f1a659..0000000 --- a/Sources/BinaryCodable/Protocols/ProtobufCodable.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -typealias ProtobufCodable = ProtobufEncodable & ProtobufDecodable - -protocol ProtobufEncodable { - - func protobufData() throws -> Data - - var protoType: String { get } - - var isZero: Bool { get } -} - -protocol ProtobufDecodable { - - init(fromProtobuf data: Data, path: [CodingKey]) throws - - static var zero: Self { get } - -} - -extension ProtobufEncodable where Self: Equatable, Self: ProtobufDecodable { - - var isZero: Bool { self == .zero } -} - - -extension ProtobufEncodable where Self: EncodablePrimitive { - - func protobufData() throws -> Data { - try data() - } -} - -extension ProtobufDecodable where Self: DecodablePrimitive { - - init(fromProtobuf data: Data, path: [CodingKey]) throws { - try self.init(decodeFrom: data, path: path) - } -} diff --git a/Sources/BinaryCodable/Protocols/ProtobufOneOf.swift b/Sources/BinaryCodable/Protocols/ProtobufOneOf.swift deleted file mode 100644 index bdf54f9..0000000 --- a/Sources/BinaryCodable/Protocols/ProtobufOneOf.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation - -/** - Add conformance to this protocol to enums which should be encoded as Protobuf `Oneof` values. - - Conform a suitable enum to the `ProtobufOneOf` protocol to indicate that it should be encoded as single value. - - The enum must have exactly one associated value for each case, and the integer keys must not overlap with the ones defined for the enclosing struct. - ```swift - struct Test: Codable { - - // The oneof field - let alternatives: OneOf - - // The OneOf definition - enum OneOf: Codable, ProtobufOneOf { - case integer(Int) - case string(String) - - // Field values, must not overlap with `Test.CodingKeys` - enum CodingKeys: Int, CodingKey { - case integer = 1 - case string = 2 - } - } - - enum CodingKeys: Int, CodingKey { - // The field id of the Oneof field is not used - case alternatives = 123456 - } - } - ``` - - See: [Protocol Buffer Language Guide: Oneof](https://developers.google.com/protocol-buffers/docs/proto3#oneof) and [Swift Protobuf: Oneof Fields](https://github.com/apple/swift-protobuf/blob/main/Documentation/API.md#oneof-fields) - */ -public protocol ProtobufOneOf { } diff --git a/Sources/BinaryCodable/Protocols/VariableLengthCodable.swift b/Sources/BinaryCodable/Protocols/VariableLengthCodable.swift index d02093c..59eb867 100644 --- a/Sources/BinaryCodable/Protocols/VariableLengthCodable.swift +++ b/Sources/BinaryCodable/Protocols/VariableLengthCodable.swift @@ -1,8 +1,13 @@ import Foundation -protocol VariableLengthCodable { - +typealias VariableLengthCodable = VariableLengthEncodable & VariableLengthDecodable + +protocol VariableLengthEncodable: FixedWidthInteger { + var variableLengthEncoding: Data { get } - - init(fromVarint data: Data, path: [CodingKey]) throws +} + +protocol VariableLengthDecodable: FixedWidthInteger { + + init(fromVarint data: Data, codingPath: [CodingKey]) throws } diff --git a/Sources/BinaryCodable/Protocols/ZigZagCodable.swift b/Sources/BinaryCodable/Protocols/ZigZagCodable.swift index a03d337..bef76e1 100644 --- a/Sources/BinaryCodable/Protocols/ZigZagCodable.swift +++ b/Sources/BinaryCodable/Protocols/ZigZagCodable.swift @@ -10,6 +10,6 @@ protocol ZigZagEncodable { protocol ZigZagDecodable { - init(fromZigZag data: Data, path: [CodingKey]) throws + init(fromZigZag data: Data, codingPath: [CodingKey]) throws } diff --git a/Sources/BinaryCodable/Wrappers/FixedSize.swift b/Sources/BinaryCodable/Wrappers/FixedSize.swift index 6b2fc39..6ae96d3 100644 --- a/Sources/BinaryCodable/Wrappers/FixedSize.swift +++ b/Sources/BinaryCodable/Wrappers/FixedSize.swift @@ -13,17 +13,13 @@ import Foundation } ``` -The `FixedSize` property wrapper is supported for `UInt32`, `UInt64`, `Int32`, and `Int64` types. +The `FixedSize` property wrapper is supported for `UInt`, `UInt32`, `UInt64`, `Int`, `Int32`, and `Int64` types. + - Warning: Do not conform other types to `FixedSizeCodable`. This will lead to crashes during encoding and decoding. - SeeAlso: [Laguage Guide (proto3): Scalar value types](https://developers.google.com/protocol-buffers/docs/proto3#scalar) */ @propertyWrapper -public struct FixedSize: ExpressibleByIntegerLiteral -where WrappedValue: FixedSizeCompatible, - WrappedValue: FixedWidthInteger, - WrappedValue: Codable { - - public typealias IntegerLiteralType = WrappedValue.IntegerLiteralType +public struct FixedSize where WrappedValue: FixedSizeCodable, WrappedValue: FixedWidthInteger { /// The value wrapped in the fixed-size container public var wrappedValue: WrappedValue @@ -35,38 +31,185 @@ where WrappedValue: FixedSizeCompatible, public init(wrappedValue: WrappedValue) { self.wrappedValue = wrappedValue } +} - public init(integerLiteral value: WrappedValue.IntegerLiteralType) { - self.wrappedValue = WrappedValue.init(integerLiteral: value) +extension FixedSize: Numeric { + public init?(exactly source: T) where T : BinaryInteger { + guard let wrapped = WrappedValue(exactly: source) else { + return nil + } + self.init(wrappedValue: wrapped) + } + + public var magnitude: WrappedValue.Magnitude { + wrappedValue.magnitude + } + + public static func * (lhs: FixedSize, rhs: FixedSize) -> FixedSize { + .init(wrappedValue: lhs.wrappedValue * rhs.wrappedValue) } + public static func *= (lhs: inout FixedSize, rhs: FixedSize) { + lhs.wrappedValue *= rhs.wrappedValue + } } -extension FixedSize: ProtobufEncodable where WrappedValue: ProtobufDecodable { +extension FixedSize: AdditiveArithmetic { - func protobufData() throws -> Data { - wrappedValue.fixedSizeEncoded + /** + The zero value. + + Zero is the identity element for addition. For any value, x + .zero == x and .zero + x == x. + */ + public static var zero: Self { + .init(wrappedValue: .zero) + } + + public static func - (lhs: FixedSize, rhs: FixedSize) -> FixedSize { + .init(wrappedValue: lhs.wrappedValue - rhs.wrappedValue) } - var protoType: String { - wrappedValue.fixedProtoType + public static func + (lhs: FixedSize, rhs: FixedSize) -> FixedSize { + .init(wrappedValue: lhs.wrappedValue + rhs.wrappedValue) } } -extension FixedSize: ProtobufDecodable where WrappedValue: ProtobufDecodable { +extension FixedSize: BinaryInteger { - static var zero: FixedSize { - .init(wrappedValue: .zero) + public init(_ source: T) where T : BinaryInteger { + self.init(wrappedValue: .init(source)) + } + + public static var isSigned: Bool { + WrappedValue.isSigned + } + + public var words: WrappedValue.Words { + wrappedValue.words + } + + public var trailingZeroBitCount: Int { + wrappedValue.trailingZeroBitCount + } + + public static func / (lhs: FixedSize, rhs: FixedSize) -> FixedSize { + .init(wrappedValue: lhs.wrappedValue / rhs.wrappedValue) + } + + public static func % (lhs: FixedSize, rhs: FixedSize) -> FixedSize { + .init(wrappedValue: lhs.wrappedValue % rhs.wrappedValue) + } + + public static func /= (lhs: inout FixedSize, rhs: FixedSize) { + lhs.wrappedValue /= rhs.wrappedValue } - init(fromProtobuf data: Data, path: [CodingKey]) throws { - let value = try WrappedValue(fromFixedSize: data, path: path) - self.init(wrappedValue: value) + public static func %= (lhs: inout FixedSize, rhs: FixedSize) { + lhs.wrappedValue %= rhs.wrappedValue + } + + public static func &= (lhs: inout FixedSize, rhs: FixedSize) { + lhs.wrappedValue &= rhs.wrappedValue + } + + public static func |= (lhs: inout FixedSize, rhs: FixedSize) { + lhs.wrappedValue |= rhs.wrappedValue + } + + public static func ^= (lhs: inout FixedSize, rhs: FixedSize) { + lhs.wrappedValue ^= rhs.wrappedValue + } +} + +extension FixedSize: FixedWidthInteger { + + public typealias Words = WrappedValue.Words + + public typealias Magnitude = WrappedValue.Magnitude + + public init(_truncatingBits source: T) where T : BinaryInteger { + self.init(wrappedValue: .init(source)) + } + + public func dividingFullWidth(_ dividend: (high: FixedSize, low: WrappedValue.Magnitude)) -> (quotient: FixedSize, remainder: FixedSize) { + let result = wrappedValue.dividingFullWidth((high: dividend.high.wrappedValue, low: dividend.low)) + return (quotient: FixedSize(wrappedValue: result.quotient), remainder: FixedSize(wrappedValue: result.remainder)) + } + + public func addingReportingOverflow(_ rhs: FixedSize) -> (partialValue: FixedSize, overflow: Bool) { + let result = wrappedValue.addingReportingOverflow(rhs.wrappedValue) + return (FixedSize(wrappedValue: result.partialValue), result.overflow) + } + + public func subtractingReportingOverflow(_ rhs: FixedSize) -> (partialValue: FixedSize, overflow: Bool) { + let result = wrappedValue.subtractingReportingOverflow(rhs.wrappedValue) + return (FixedSize(wrappedValue: result.partialValue), result.overflow) + } + + public func multipliedReportingOverflow(by rhs: FixedSize) -> (partialValue: FixedSize, overflow: Bool) { + let result = wrappedValue.multipliedReportingOverflow(by: rhs.wrappedValue) + return (FixedSize(wrappedValue: result.partialValue), result.overflow) + } + + public func dividedReportingOverflow(by rhs: FixedSize) -> (partialValue: FixedSize, overflow: Bool) { + let result = wrappedValue.dividedReportingOverflow(by: rhs.wrappedValue) + return (FixedSize(wrappedValue: result.partialValue), result.overflow) + } + + public func remainderReportingOverflow(dividingBy rhs: FixedSize) -> (partialValue: FixedSize, overflow: Bool) { + let result = wrappedValue.remainderReportingOverflow(dividingBy: rhs.wrappedValue) + return (FixedSize(wrappedValue: result.partialValue), result.overflow) + } + + public static var bitWidth: Int { + WrappedValue.bitWidth + } + + public var nonzeroBitCount: Int { + wrappedValue.nonzeroBitCount + } + + public var leadingZeroBitCount: Int { + wrappedValue.leadingZeroBitCount + } + + public var byteSwapped: FixedSize { + .init(wrappedValue: wrappedValue.byteSwapped) + } + + /// The maximum representable integer in this type. + /// + /// For unsigned integer types, this value is `(2 ** bitWidth) - 1`, where + /// `**` is exponentiation. For signed integer types, this value is + /// `(2 ** (bitWidth - 1)) - 1`. + public static var max: Self { + .init(wrappedValue: .max) + } + + /// The minimum representable integer in this type. + /// + /// For unsigned integer types, this value is always `0`. For signed integer + /// types, this value is `-(2 ** (bitWidth - 1))`, where `**` is + /// exponentiation. + public static var min: Self { + .init(wrappedValue: .min) + } +} + +extension FixedSize: ExpressibleByIntegerLiteral { + + public typealias IntegerLiteralType = WrappedValue.IntegerLiteralType + + public init(integerLiteral value: WrappedValue.IntegerLiteralType) { + self.wrappedValue = WrappedValue.init(integerLiteral: value) } } extension FixedSize: Equatable { + public static func == (lhs: FixedSize, rhs: FixedSize) -> Bool { + lhs.wrappedValue == rhs.wrappedValue + } } extension FixedSize: Comparable { @@ -78,24 +221,22 @@ extension FixedSize: Comparable { extension FixedSize: Hashable { } -extension FixedSize: CodablePrimitive, DataTypeProvider where WrappedValue: DataTypeProvider { +extension FixedSize: EncodablePrimitive where WrappedValue: EncodablePrimitive { /** Encode the wrapped value to binary data compatible with the protobuf encoding. - Returns: The binary data in host-independent format. */ - func data() -> Data { + var encodedData: Data { wrappedValue.fixedSizeEncoded } +} - init(decodeFrom data: Data, path: [CodingKey]) throws { - let wrappedValue = try WrappedValue(fromFixedSize: data, path: path) - self.init(wrappedValue: wrappedValue) - } +extension FixedSize: DecodablePrimitive where WrappedValue: DecodablePrimitive { - /// The wire type of the wrapped value. - static var dataType: DataType { - WrappedValue.fixedSizeDataType + init(data: Data, codingPath: [CodingKey]) throws { + let wrappedValue = try WrappedValue(fromFixedSize: data, codingPath: codingPath) + self.init(wrappedValue: wrappedValue) } } @@ -123,33 +264,3 @@ extension FixedSize: Decodable { self = try container.decode(Self.self) } } - -public extension FixedSize { - - /// The maximum representable integer in this type. - /// - /// For unsigned integer types, this value is `(2 ** bitWidth) - 1`, where - /// `**` is exponentiation. For signed integer types, this value is - /// `(2 ** (bitWidth - 1)) - 1`. - static var max: Self { - .init(wrappedValue: .max) - } - - /// The minimum representable integer in this type. - /// - /// For unsigned integer types, this value is always `0`. For signed integer - /// types, this value is `-(2 ** (bitWidth - 1))`, where `**` is - /// exponentiation. - static var min: Self { - .init(wrappedValue: .min) - } - - /** - The zero value. - - Zero is the identity element for addition. For any value, x + .zero == x and .zero + x == x. - */ - static var zero: Self { - .init(wrappedValue: .zero) - } -} diff --git a/Sources/BinaryCodable/Wrappers/FixedSizeCodable.swift b/Sources/BinaryCodable/Wrappers/FixedSizeCodable.swift new file mode 100644 index 0000000..95dc006 --- /dev/null +++ b/Sources/BinaryCodable/Wrappers/FixedSizeCodable.swift @@ -0,0 +1,20 @@ +import Foundation + +public typealias FixedSizeCodable = FixedSizeEncodable & FixedSizeDecodable + +/// An integer type which can be forced to use a fixed-length encoding instead of variable-length encoding. +public protocol FixedSizeEncodable: Encodable { + + /// The value encoded as fixed size binary data + var fixedSizeEncoded: Data { get } +} + +public protocol FixedSizeDecodable: Decodable { + + /** + Decode the value from binary data. + - Parameter data: The binary data of the correct size for the type. + - Throws: `DecodingError` + */ + init(fromFixedSize data: Data, codingPath: [CodingKey]) throws +} diff --git a/Sources/BinaryCodable/Wrappers/FixedSizeCompatible.swift b/Sources/BinaryCodable/Wrappers/FixedSizeCompatible.swift deleted file mode 100644 index ae30bb9..0000000 --- a/Sources/BinaryCodable/Wrappers/FixedSizeCompatible.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -/// An integer type which can be forced to use a fixed-length encoding instead of variable-length encoding. -public protocol FixedSizeCompatible { - - /// The wire type of the type, which has a constant length - static var fixedSizeDataType: DataType { get } - - /// The protobuf type equivalent to the fixed size type - var fixedProtoType: String { get } - - /// The value encoded as fixed size binary data - var fixedSizeEncoded: Data { get } - - /** - Decode the value from binary data. - - Parameter data: The binary data of the correct size for the type. - - Throws: `DecodingError` - */ - init(fromFixedSize data: Data, path: [CodingKey]) throws -} - -extension FixedSizeCompatible { - - /// The wire type of the value, which has a constant length - var fixedSizeDataType: DataType { - Self.fixedSizeDataType - } -} diff --git a/Sources/BinaryCodable/Wrappers/Primitives/Int+FixedSize.swift b/Sources/BinaryCodable/Wrappers/Primitives/Int+FixedSize.swift new file mode 100644 index 0000000..7405965 --- /dev/null +++ b/Sources/BinaryCodable/Wrappers/Primitives/Int+FixedSize.swift @@ -0,0 +1,19 @@ +import Foundation + +extension Int: FixedSizeEncodable { + + public var fixedSizeEncoded: Data { + Int64(self).fixedSizeEncoded + } +} + +extension Int: FixedSizeDecodable { + + public init(fromFixedSize data: Data, codingPath: [CodingKey]) throws { + let signed = try Int64(fromFixedSize: data, codingPath: codingPath) + guard let value = Int(exactly: signed) else { + throw DecodingError.variableLengthEncodedIntegerOutOfRange(codingPath) + } + self = value + } +} diff --git a/Sources/BinaryCodable/Wrappers/Primitives/Int32+FixedSize.swift b/Sources/BinaryCodable/Wrappers/Primitives/Int32+FixedSize.swift new file mode 100644 index 0000000..2260daa --- /dev/null +++ b/Sources/BinaryCodable/Wrappers/Primitives/Int32+FixedSize.swift @@ -0,0 +1,20 @@ +import Foundation + +extension Int32: FixedSizeEncodable { + + public var fixedSizeEncoded: Data { + let value = UInt32(bitPattern: littleEndian) + return Data(underlying: value) + } +} + +extension Int32: FixedSizeDecodable { + + public init(fromFixedSize data: Data, codingPath: [CodingKey]) throws { + guard data.count == MemoryLayout.size else { + throw DecodingError.invalidSize(size: data.count, for: "Int32", codingPath: codingPath) + } + let value = UInt32(littleEndian: data.interpreted()) + self.init(bitPattern: value) + } +} diff --git a/Sources/BinaryCodable/Wrappers/Primitives/Int64+FixedSize.swift b/Sources/BinaryCodable/Wrappers/Primitives/Int64+FixedSize.swift new file mode 100644 index 0000000..cf0b9bb --- /dev/null +++ b/Sources/BinaryCodable/Wrappers/Primitives/Int64+FixedSize.swift @@ -0,0 +1,20 @@ +import Foundation + +extension Int64: FixedSizeEncodable { + + public var fixedSizeEncoded: Data { + let value = UInt64(bitPattern: littleEndian) + return Data.init(underlying: value) + } +} + +extension Int64: FixedSizeDecodable { + + public init(fromFixedSize data: Data, codingPath: [CodingKey]) throws { + guard data.count == MemoryLayout.size else { + throw DecodingError.invalidSize(size: data.count, for: "Int64", codingPath: codingPath) + } + let value = UInt64(littleEndian: data.interpreted()) + self.init(bitPattern: value) + } +} diff --git a/Sources/BinaryCodable/Wrappers/Primitives/UInt+FixedSize.swift b/Sources/BinaryCodable/Wrappers/Primitives/UInt+FixedSize.swift new file mode 100644 index 0000000..fdf0f1e --- /dev/null +++ b/Sources/BinaryCodable/Wrappers/Primitives/UInt+FixedSize.swift @@ -0,0 +1,20 @@ +import Foundation + +extension UInt: FixedSizeEncodable { + + public var fixedSizeEncoded: Data { + UInt64(self).fixedSizeEncoded + } +} + +extension UInt: FixedSizeDecodable { + + public init(fromFixedSize data: Data, codingPath: [CodingKey]) throws { + let intValue = try UInt64(fromFixedSize: data, codingPath: codingPath) + guard let value = UInt(exactly: intValue) else { + throw DecodingError.variableLengthEncodedIntegerOutOfRange(codingPath) + } + self = value + } +} + diff --git a/Sources/BinaryCodable/Wrappers/Primitives/UInt32+FixedSize.swift b/Sources/BinaryCodable/Wrappers/Primitives/UInt32+FixedSize.swift new file mode 100644 index 0000000..29ce44e --- /dev/null +++ b/Sources/BinaryCodable/Wrappers/Primitives/UInt32+FixedSize.swift @@ -0,0 +1,18 @@ +import Foundation + +extension UInt32: FixedSizeEncodable { + + public var fixedSizeEncoded: Data { + Data(underlying: littleEndian) + } +} + +extension UInt32: FixedSizeDecodable { + + public init(fromFixedSize data: Data, codingPath: [CodingKey]) throws { + guard data.count == MemoryLayout.size else { + throw DecodingError.invalidSize(size: data.count, for: "UInt32", codingPath: codingPath) + } + self.init(littleEndian: data.interpreted()) + } +} diff --git a/Sources/BinaryCodable/Wrappers/Primitives/UInt64+FixedSize.swift b/Sources/BinaryCodable/Wrappers/Primitives/UInt64+FixedSize.swift new file mode 100644 index 0000000..df3fdd9 --- /dev/null +++ b/Sources/BinaryCodable/Wrappers/Primitives/UInt64+FixedSize.swift @@ -0,0 +1,18 @@ +import Foundation + +extension UInt64: FixedSizeEncodable { + + public var fixedSizeEncoded: Data { + Data(underlying: littleEndian) + } +} + +extension UInt64: FixedSizeDecodable { + + public init(fromFixedSize data: Data, codingPath: [CodingKey]) throws { + guard data.count == MemoryLayout.size else { + throw DecodingError.invalidSize(size: data.count, for: "UInt64", codingPath: codingPath) + } + self.init(littleEndian: data.interpreted()) + } +} diff --git a/Sources/BinaryCodable/Wrappers/SignedValue.swift b/Sources/BinaryCodable/Wrappers/SignedValue.swift deleted file mode 100644 index d65773a..0000000 --- a/Sources/BinaryCodable/Wrappers/SignedValue.swift +++ /dev/null @@ -1,156 +0,0 @@ -import Foundation - -/** - A wrapper for integers more efficient for negative values. - - This encoding format enforces `Zig-Zag` encoding, which is more efficient when numbers are often negative. - - - Note: This wrapper is only useful when encoding and decoding to/from protobuf data. - It has no effect for the standard `BinaryEncoder` and `BinaryDecoder`, where integer values are - encoded using `Zig-Zag` encoding by default. - - Use the property wrapped within a `Codable` definition to enforce fixed-width encoding for a property: - ```swift - struct MyStruct: Codable { - - /// Efficient for small positive and negative values - @SignedValue - var count: Int32 - } - ``` - -The `SignedValue` property wrapper is supported for `Int`, `Int32`, and `Int64` types. - - - SeeAlso: [Laguage Guide (proto3): Scalar value types](https://developers.google.com/protocol-buffers/docs/proto3#scalar) - */ -@propertyWrapper -public struct SignedValue: ExpressibleByIntegerLiteral -where WrappedValue: SignedValueCompatible, - WrappedValue: SignedInteger, - WrappedValue: FixedWidthInteger, - WrappedValue: Codable { - - public typealias IntegerLiteralType = WrappedValue.IntegerLiteralType - - /// The value wrapped in the fixed-size container - public var wrappedValue: WrappedValue - - /** - Wrap an integer value in a fixed-size container - - Parameter wrappedValue: The integer to wrap - */ - public init(wrappedValue: WrappedValue) { - self.wrappedValue = wrappedValue - } - - public init(integerLiteral value: WrappedValue.IntegerLiteralType) { - self.wrappedValue = WrappedValue.init(integerLiteral: value) - } -} - -extension SignedValue: Equatable { - -} - -extension SignedValue: Comparable { - - public static func < (lhs: SignedValue, rhs: SignedValue) -> Bool { - lhs.wrappedValue < rhs.wrappedValue - } -} - -extension SignedValue: Hashable { - -} - -extension SignedValue: CodablePrimitive, DataTypeProvider where WrappedValue: ZigZagCodable, WrappedValue: DataTypeProvider { - - /** - Encode the wrapped value to binary data compatible with the protobuf encoding. - - Returns: The binary data in host-independent format. - */ - func data() -> Data { - wrappedValue.zigZagEncoded - } - - init(decodeFrom data: Data, path: [CodingKey]) throws { - let wrappedValue = try WrappedValue(fromZigZag: data, path: path) - self.init(wrappedValue: wrappedValue) - } - - /// The wire type of the wrapped value. - static var dataType: DataType { - WrappedValue.dataType - } -} - -extension SignedValue: Encodable { - - /** - Encode the wrapped value transparently to the given encoder. - - Parameter encoder: The encoder to use for encoding. - - Throws: Errors from the decoder when attempting to encode a value in a single value container. - */ - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self) - } -} - -extension SignedValue: Decodable { - /** - Decode a wrapped value from a decoder. - - Parameter decoder: The decoder to use for decoding. - - Throws: Errors from the decoder when reading a single value container or decoding the wrapped value from it. - */ - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - self = try container.decode(Self.self) - } -} - -public extension SignedValue { - - /** - The zero value. - - Zero is the identity element for addition. For any value, `x + .zero == x` and `.zero + x == x`. - */ - static var zero: Self { - .init(wrappedValue: .zero) - } - - /// The maximum representable integer in this type. - /// - /// For unsigned integer types, this value is `(2 ** bitWidth) - 1`, where - /// `**` is exponentiation. For signed integer types, this value is - /// `(2 ** (bitWidth - 1)) - 1`. - static var max: Self { - .init(wrappedValue: .max) - } - - /// The minimum representable integer in this type. - /// - /// For unsigned integer types, this value is always `0`. For signed integer - /// types, this value is `-(2 ** (bitWidth - 1))`, where `**` is - /// exponentiation. - static var min: Self { - .init(wrappedValue: .min) - } -} - -extension SignedValue: ProtobufCodable where WrappedValue: ZigZagCodable, WrappedValue: SignedValueCompatible { - - func protobufData() throws -> Data { - wrappedValue.zigZagEncoded - } - - init(fromProtobuf data: Data, path: [CodingKey]) throws { - let value = try WrappedValue(fromZigZag: data, path: path) - self.init(wrappedValue: value) - } - - var protoType: String { - wrappedValue.positiveProtoType - } -} diff --git a/Sources/BinaryCodable/Wrappers/SignedValueCompatible.swift b/Sources/BinaryCodable/Wrappers/SignedValueCompatible.swift deleted file mode 100644 index 6c0076b..0000000 --- a/Sources/BinaryCodable/Wrappers/SignedValueCompatible.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -/// A signed integer which can be forced to use zig-zag encoding. -public protocol SignedValueCompatible { - - var positiveProtoType: String { get } -} diff --git a/Tests/BinaryCodableTests/ArrayEncodingTests.swift b/Tests/BinaryCodableTests/ArrayEncodingTests.swift index c17bcdb..40a3a07 100644 --- a/Tests/BinaryCodableTests/ArrayEncodingTests.swift +++ b/Tests/BinaryCodableTests/ArrayEncodingTests.swift @@ -2,155 +2,180 @@ import XCTest @testable import BinaryCodable final class ArrayEncodingTests: XCTestCase { - + func testBoolArrayEncoding() throws { - try compareArray(Bool.self, values: [true, false, false], - to: [1, 0, 0]) - } - - func testInt8Encoding() throws { - try compareArray(Int8.self, values: [.zero, 123, .min, .max, -1], - to: [0, 123, 128, 127, 255]) - } - - func testInt16Encoding() throws { - try compareArray(Int16.self, values: [.zero, 123, .min, .max, -1], - to: [0, 0, 123, 0, 0, 128, 255, 127, 255, 255]) - } - - func testInt32Encoding() throws { - try compareArray(Int32.self, values: [.zero, 123, .min, .max, -1], - to: [0, // 0 - 246, 1, // 123 - 255, 255, 255, 255, 15, // -2.147.483.648 - 254, 255, 255, 255, 15, // 2.147.483.647 - 1]) // -1 - } - - func testInt64Encoding() throws { - try compareArray(Int64.self, values: [0, 123, .max, .min, -1], - to: [0, // 0 - 246, 1, // 123 - 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // max - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // min - 1]) // 1 - } - - func testIntEncoding() throws { - try compareArray(Int.self, values: [0, 123, .max, .min, -1], - to: [0, // 0 - 246, 1, // 123 - 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // max - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // min - 1]) // 1 - } - - func testUInt8Encoding() throws { - try compareArray(UInt8.self, values: [.zero, 123, .min, .max], - to: [0, 123, 0, 255]) - } - - func testUInt16Encoding() throws { - try compareArray(UInt16.self, values: [.zero, 123, .min, .max, 12345], - to: [0, 0, - 123, 0, - 0, 0, - 255, 255, - 0x39, 0x30]) - } - - func testUInt32Encoding() throws { - try compareArray(UInt32.self, values: [.zero, 123, .min, 12345, 123456, 12345678, 1234567890, .max], - to: [0, - 123, - 0, - 0xB9, 0x60, - 0xC0, 0xC4, 0x07, - 0xCE, 0xC2, 0xF1, 0x05, - 0xD2, 0x85, 0xD8, 0xCC, 0x04, - 255, 255, 255, 255, 15]) - } - - func testUInt64Encoding() throws { - try compareArray(UInt64.self, values: [.zero, 123, .min, 12345, 123456, 12345678, 1234567890, 12345678901234, .max], - to: [0, - 123, - 0, - 0xB9, 0x60, - 0xC0, 0xC4, 0x07, - 0xCE, 0xC2, 0xF1, 0x05, - 0xD2, 0x85, 0xD8, 0xCC, 0x04, - 0xF2, 0xDF, 0xB8, 0x9E, 0xA7, 0xE7, 0x02, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) - } - - func testUIntEncoding() throws { - try compareArray(UInt.self, values: [.zero, 123, .min, 12345, 123456, 12345678, 1234567890, 12345678901234, .max], - to: [0, - 123, - 0, - 0xB9, 0x60, - 0xC0, 0xC4, 0x07, - 0xCE, 0xC2, 0xF1, 0x05, - 0xD2, 0x85, 0xD8, 0xCC, 0x04, - 0xF2, 0xDF, 0xB8, 0x9E, 0xA7, 0xE7, 0x02, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) - } - - func testStringEncoding() throws { + try compare([true, false, false], to: [ + 2, 1, + 2, 0, + 2, 0 + ]) + } + + func testInt8ArrayEncoding() throws { + try compare([.zero, 123, .min, .max, -1], of: [Int8].self, to: [ + 2, 0, + 2, 123, + 2, 128, + 2, 127, + 2, 255 + ]) + } + + func testInt16ArrayEncoding() throws { + try compare([.zero, 123, .min, .max, -1], of: [Int16].self, to: [ + 4, 0, 0, + 4, 123, 0, + 4, 0, 128, + 4, 255, 127, + 4, 255, 255 + ]) + } + + func testInt32ArrayEncoding() throws { + try compare([.zero, 123, .min, .max, -1], of: [Int32].self, to: [ + 2, 0, // 0 + 4, 246, 1, // 123 + 10, 255, 255, 255, 255, 15, // -2.147.483.648 + 10, 254, 255, 255, 255, 15, // 2.147.483.647 + 2, 1]) // -1 + } + + func testInt64ArrayEncoding() throws { + try compare([0, 123, .max, .min, -1], of: [Int64].self, to: [ + 2, 0, // 0 + 4, 246, 1, // 123 + 18, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // max + 18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // min + 2, 1]) // 1 + } + + func testIntArrayEncoding() throws { + try compare([0, 123, .max, .min, -1], of: [Int].self, to: [ + 2, 0, // 0 + 4, 246, 1, // 123 + 18, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // max + 18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // min + 2, 1]) // 1 + } + + func testUInt8ArrayEncoding() throws { + try compare([.zero, 123, .min, .max], of: [UInt8].self, to: [ + 2, 0, + 2, 123, + 2, 0, + 2, 255 + ]) + } + + func testUInt16ArrayEncoding() throws { + try compare([.zero, 123, .min, .max, 12345], of: [UInt16].self, to: [ + 4, 0, 0, + 4, 123, 0, + 4, 0, 0, + 4, 255, 255, + 4, 0x39, 0x30 + ]) + } + + func testUInt32ArrayEncoding() throws { + try compare([.zero, 123, .min, 12345, 123456, 12345678, 1234567890, .max], of: [UInt32].self, to: [ + 2, 0, + 2, 123, + 2, 0, + 4, 0xB9, 0x60, + 6, 0xC0, 0xC4, 0x07, + 8, 0xCE, 0xC2, 0xF1, 0x05, + 10, 0xD2, 0x85, 0xD8, 0xCC, 0x04, + 10, 255, 255, 255, 255, 15]) + } + + func testUInt64ArrayEncoding() throws { + try compare([.zero, 123, .min, 12345, 123456, 12345678, 1234567890, 12345678901234, .max], of: [UInt64].self, to: [ + 2, 0, + 2, 123, + 2, 0, + 4, 0xB9, 0x60, + 6, 0xC0, 0xC4, 0x07, + 8, 0xCE, 0xC2, 0xF1, 0x05, + 10, 0xD2, 0x85, 0xD8, 0xCC, 0x04, + 14, 0xF2, 0xDF, 0xB8, 0x9E, 0xA7, 0xE7, 0x02, + 18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + } + + func testUIntArrayEncoding() throws { + try compare([.zero, 123, .min, 12345, 123456, 12345678, 1234567890, 12345678901234, .max], of: [UInt].self, to: [ + 2, 0, + 2, 123, + 2, 0, + 4, 0xB9, 0x60, + 6, 0xC0, 0xC4, 0x07, + 8, 0xCE, 0xC2, 0xF1, 0x05, + 10, 0xD2, 0x85, 0xD8, 0xCC, 0x04, + 14, 0xF2, 0xDF, 0xB8, 0x9E, 0xA7, 0xE7, 0x02, + 18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + } + + func testStringArrayEncoding() throws { let values = ["Some", "A longer text with\n multiple lines", "More text", "eolqjwqu(Jan?!)§(!N"] let result = values.map { value -> [UInt8] in let data = Array(value.data(using: .utf8)!) - return data.count.variableLengthEncoding + data + return [UInt8(data.count * 2)] + data }.reduce([], +) - try compareArray(String.self, values: values, to: result) - } - - func testFloatEncoding() throws { - try compareArray(Float.self, values: [.greatestFiniteMagnitude, .zero, .pi, -.pi, .leastNonzeroMagnitude], - to: [0x7F, 0x7F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, - 0x40, 0x49, 0x0F, 0xDA, - 0xC0, 0x49, 0x0F, 0xDA, - 0x00, 0x00, 0x00, 0x01]) - } - - func testDoubleEncoding() throws { - try compareArray(Double.self, values: [.greatestFiniteMagnitude, .zero, .pi, .leastNonzeroMagnitude, -.pi], - to: [0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18]) - } - + try compare(values, to: result) + } + + func testFloatArrayEncoding() throws { + try compare([.greatestFiniteMagnitude, .zero, .pi, -.pi, .leastNonzeroMagnitude], of: [Float].self, to: [ + 8, 0x7F, 0x7F, 0xFF, 0xFF, + 8, 0x00, 0x00, 0x00, 0x00, + 8, 0x40, 0x49, 0x0F, 0xDA, + 8, 0xC0, 0x49, 0x0F, 0xDA, + 8, 0x00, 0x00, 0x00, 0x01]) + } + + func testDoubleArrayEncoding() throws { + try compare([.greatestFiniteMagnitude, .zero, .pi, .leastNonzeroMagnitude, -.pi], of: [Double].self, to: [ + 16, 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 16, 0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18, + 16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 16, 0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18]) + } + func testArrayOfOptionalsEncoding() throws { - try compareArray(Bool?.self, values: [true, false, nil, true, nil, false], - to: [1, 1, 1, 0, 0, 1, 1, 0, 1, 0]) + try compare([true, false, nil, true, nil, false], of: [Bool?].self, to: [ + 2, 1, + 2, 0, + 1, + 2, 1, + 1, + 2, 0 + ]) } func testArrayOfDoubleOptionalsEncoding() throws { - try compareArray(Bool??.self, values: [.some(.some(true)), .some(.some(false)), .some(.none), .none], - to: [1, 1, 1, - 1, 1, 0, - 1, 0, - 0]) + try compare([.some(.some(true)), .some(.some(false)), .some(.none), .none], of: [Bool??].self, to: [ + 4, 0, 1, + 4, 0, 0, + 2, 1, + 1]) } func testArrayOfTripleOptionalsEncoding() throws { - try compareArray(Bool???.self, values: [.some(.some(.some(true))), .some(.some(.some(false))), .some(.some(.none)), .some(.none), .none], - to: [1, 1, 1, 1, - 1, 1, 1, 0, - 1, 1, 0, - 1, 0, - 0]) - } - + try compare([.some(.some(.some(true))), .some(.some(.some(false))), .some(.some(.none)), .some(.none), .none], of: [Bool???].self, to: [ + 6, 0, 0, 1, + 6, 0, 0, 0, + 4, 0, 1, + 2, 1, + 1]) + } + func testArrayOfArraysEncoding() throws { let values: [[Bool]] = [[false], [true, false]] - try compareArray([Bool].self, values: values, - to: [1, 0, - 2, 1, 0]) + try compare(values, of: [[Bool]].self, to: [ + 4, 2, 0, + 8, 2, 1, 2, 0 + ]) } func testDataEncoding() throws { @@ -159,63 +184,4 @@ final class ArrayEncodingTests: XCTestCase { try compare(data, to: expected) try compare(Data(), to: []) } - - private func compareWithIndexSet(_ input: T, bytes: [UInt8]) throws where T: Codable, T: Equatable { - let encoder = BinaryEncoder() - encoder.prependNilIndexSetForUnkeyedContainers = true - let decoder = BinaryDecoder() - decoder.containsNilIndexSetForUnkeyedContainers = true - - let encoded = try encoder.encode(input) - XCTAssertEqual(encoded.bytes, bytes) - let decoded = try decoder.decode(T.self, from: encoded) - XCTAssertEqual(input, decoded) - } - - func testEncodingWithNilSet() throws { - try compareWithIndexSet([1,2,3], bytes: [0, 2, 4, 6]) - } - - func testEncodingOptionalsWithNilSet() throws { - let c = UnkeyedOptionalContainer(values: [1, nil, 2, nil, 3]) - try compareWithIndexSet(c, bytes: [2, 1, 3, 2, 4, 6]) - } -} - -private struct UnkeyedOptionalContainer: Equatable { - - let values: [Int?] - -} - -extension UnkeyedOptionalContainer: Codable { - - func encode(to encoder: Encoder) throws { - var container = encoder.unkeyedContainer() - for value in values { - if let value { - try container.encode(value) - } else { - try container.encodeNil() - } - } - } - - enum CodingKeys: CodingKey { - case values - } - - init(from decoder: Decoder) throws { - var container = try decoder.unkeyedContainer() - var values: [Int?] = [] - while !container.isAtEnd { - if try container.decodeNil() { - values.append(nil) - } else { - let value = try container.decode(Int.self) - values.append(value) - } - } - self.values = values - } } diff --git a/Tests/BinaryCodableTests/BoolTests.swift b/Tests/BinaryCodableTests/BoolTests.swift new file mode 100644 index 0000000..c093478 --- /dev/null +++ b/Tests/BinaryCodableTests/BoolTests.swift @@ -0,0 +1,85 @@ +import XCTest +@testable import BinaryCodable + +final class BoolTests: XCTestCase { + + func testBoolAtTopLevel() throws { + try compare(false, to: [0]) + try compare(true, to: [1]) + } + + func testOptionalBool() throws { + try compare(false, of: Bool?.self, to: [0, 0]) + try compare(true, of: Bool?.self, to: [0, 1]) + try compare(nil, of: Bool?.self, to: [1]) + } + + func testArrayBool() throws { + try compare([false], to: [2, 0]) + try compare([true], to: [2, 1]) + try compare([true, false], to: [2, 1, 2, 0]) + try compare([false, true], to: [2, 0, 2, 1]) + } + + func testArrayOptionalBool() throws { + try compare([false], of: [Bool?].self, to: [2, 0]) + try compare([true], of: [Bool?].self, to: [2, 1]) + try compare([true, false], of: [Bool?].self, to: [2, 1, 2, 0]) + try compare([false, true], of: [Bool?].self, to: [2, 0, 2, 1]) + try compare([nil], of: [Bool?].self, to: [1]) + try compare([nil, nil], of: [Bool?].self, to: [1, 1]) + try compare([false, nil], of: [Bool?].self, to: [2, 0, 1]) + try compare([nil, true], of: [Bool?].self, to: [1, 2, 1]) + } + + func testOptionalArrayBool() throws { + try compare(nil, of: [Bool]?.self, to: [1]) + try compare([false], of: [Bool]?.self, to: [0, 2, 0]) + try compare([true], of: [Bool]?.self, to: [0, 2, 1]) + try compare([true, false], of: [Bool]?.self, to: [0, 2, 1, 2, 0]) + try compare([false, true], of: [Bool]?.self, to: [0, 2, 0, 2, 1]) + } + + func testDoubleOptionalBool() throws { + try compare(nil, of: Bool??.self, to: [1]) + try compare(.some(nil), of: Bool??.self, to: [0, 1]) + try compare(true, of: Bool??.self, to: [0, 0, 1]) + try compare(false, of: Bool??.self, to: [0, 0, 0]) + } + + func testTripleOptionalBool() throws { + try compare(nil, of: Bool???.self, to: [1]) + try compare(.some(nil), of: Bool???.self, to: [0, 1]) + try compare(.some(.some(nil)), of: Bool???.self, to: [0, 0, 1]) + try compare(true, of: Bool???.self, to: [0, 0, 0, 1]) + try compare(false, of: Bool???.self, to: [0, 0, 0, 0]) + } + + func testStructWithBool() throws { + struct Test: Codable, Equatable { + let value: Bool + enum CodingKeys: Int, CodingKey { case value = 1 } + } + + let expected: [UInt8] = [ + 2, // Int key 1 (2x, String bit 0) + 2, // Length of value (2x, Nil bit 0) + 1, // Value + ] + try compare(Test(value: true), to: expected) + } + + func testUnkeyedWithBool() throws { + GenericTestStruct.encode { encoder in + var container = encoder.unkeyedContainer() + try container.encode(true) + } + GenericTestStruct.decode { decoder in + var container = try decoder.unkeyedContainer() + let value = try container.decode(Bool.self) + XCTAssertEqual(value, true) + } + } + + +} diff --git a/Tests/BinaryCodableTests/CodingPathTests.swift b/Tests/BinaryCodableTests/CodingPathTests.swift index 203d7c1..1ae532a 100644 --- a/Tests/BinaryCodableTests/CodingPathTests.swift +++ b/Tests/BinaryCodableTests/CodingPathTests.swift @@ -1,339 +1,40 @@ import XCTest -import BinaryCodable +@testable import BinaryCodable +/** + Tests to ensure that coding paths are correctly set. + */ final class CodingPathTests: XCTestCase { - struct KeyedBox: Codable { - enum CodingKeys: String, CodingKey { - case val - } - - let val: T - - init(_ val: T) { - self.val = val - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.val = try container.decode(T.self, forKey: .val) - } - } - - struct CorruptBool: Codable, Equatable, ExpressibleByBooleanLiteral { - let val: Bool - - init(booleanLiteral value: BooleanLiteralType) { - self.val = value - } - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - self.val = try container.decode(Bool.self) - if val == false { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Found false!") - } - } - - func encode(to encoder: Encoder) throws { + func testCodingPathAtRoot() throws { + GenericTestStruct.encode { encoder in + // Need to set some value, otherwise encoding will fail var container = encoder.singleValueContainer() - try container.encode(val) + try container.encode(0) + XCTAssertEqual(encoder.codingPath, []) } - } - - func testStructWithCorruptArrayElement() throws { - struct Test: Codable, Equatable { - let arr: [CorruptBool] - } - - let corruptedBytes: [UInt8] = [ - 0b00111010, 118, 97, 108, // String key 'val', varint - 8, // Length of val - 0b00111010, 97, 114, 114, // String key 'arr', varint - 3, // 3 elements - 1, 1, 0 // True, true, corrupt! - ] - - let data = try BinaryEncoder().encode(KeyedBox(Test(arr: [true, true, false]))) - XCTAssertEqual(Array(data), corruptedBytes) - - do { - let _ = try BinaryDecoder().decode(KeyedBox.self, from: data) - XCTFail("Unexpected succeded!") - } catch let error as DecodingError { - guard case .dataCorrupted(let context) = error else { - XCTFail("Unexpected error!") - return - } - XCTAssertEqual(context.codingPath.map { $0.stringValue }, ["val", "arr", "2"]) + GenericTestStruct.decode { decoder in + XCTAssertEqual(decoder.codingPath, []) } + try compare(GenericTestStruct()) } - func testStructWithArrayMissingLastElement() throws { - struct Test: Codable, Equatable { - let arr: [Bool] + func testCodingPathInKeyedContainer() throws { + enum SomeKey: Int, CodingKey { + case value = 1 } - - let corruptedBytes: [UInt8] = [ - 0b00111010, 118, 97, 108, // String key 'val', varint - 8, // Length of val - 0b00111010, 97, 114, 114, // String key 'arr', varint - 3, // 3 elements - 1, 1 // Only two elements provided! - ] - - var data = try BinaryEncoder().encode(KeyedBox(Test(arr: [true, true, false]))) - data.removeLast() - XCTAssertEqual(Array(data), corruptedBytes) - - do { - let _ = try BinaryDecoder().decode(KeyedBox.self, from: Data(corruptedBytes)) - XCTFail("Unexpected succeded!") - } catch let error as DecodingError { - guard case .dataCorrupted(let context) = error else { - XCTFail("Unexpected error!") - return - } - XCTAssertEqual(context.codingPath.map { $0.stringValue }, ["val"]) - } - } - - func testStructWithArrayMissingLastElementButCorrectLength() throws { - struct Test: Codable, Equatable { - let arr: [Bool] - } - - let corruptedBytes: [UInt8] = [ - 0b00111010, 118, 97, 108, // String key 'val', varint - 7, // Length of val - 0b00111010, 97, 114, 114, // String key 'arr', varint - 3, // 3 elements - 1, 1 // Only two elements provided! - ] - - var data = try BinaryEncoder().encode(KeyedBox(Test(arr: [true, true, false]))) - data[4] -= 1 - data.removeLast() - XCTAssertEqual(Array(data), corruptedBytes) - - do { - let _ = try BinaryDecoder().decode(KeyedBox.self, from: Data(corruptedBytes)) - XCTFail("Unexpected succeded!") - } catch let error as DecodingError { - guard case .dataCorrupted(let context) = error else { - XCTFail("Unexpected error!") - return - } - XCTAssertEqual(context.codingPath.map { $0.stringValue }, ["val", "arr"]) + GenericTestStruct.encode { encoder in + XCTAssertEqual(encoder.codingPath, []) + var container = encoder.container(keyedBy: SomeKey.self) + let unkeyed = container.nestedUnkeyedContainer(forKey: .value) + XCTAssertEqual(unkeyed.codingPath, [1]) } - } - - func testStructWithCorruptDataOnKeyedNestedContainer() throws { - struct Test: Codable, Equatable { - enum CodingKeys: String, CodingKey { - case nested - } - - enum NestedCodingKeys: String, CodingKey { - case bool - } - - let bool: CorruptBool - - init(bool: CorruptBool) { - self.bool = bool - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let nestedContainer = try container.nestedContainer(keyedBy: NestedCodingKeys.self, forKey: .nested) - self.bool = try nestedContainer.decode(CorruptBool.self, forKey: .bool) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - var nestedContainer = container.nestedContainer(keyedBy: NestedCodingKeys.self, forKey: .nested) - try nestedContainer.encode(bool, forKey: .bool) - } - } - - let corruptedBytes: [UInt8] = [ - 0b00111010, 118, 97, 108, // String key 'val', varint - 14, // Byte length of val value - 0b01101010, 110, 101, 115, 116, 101, 100, // String key 'nested', varint - 6, // Byte length of nested value - 0b01001000, 98, 111, 111, 108, // String key 'bool', varint - 0, // Corrupt! - ] - - let data = try BinaryEncoder().encode(KeyedBox(Test(bool: false))) - XCTAssertEqual(Array(data), corruptedBytes) - - do { - let _ = try BinaryDecoder().decode(KeyedBox.self, from: data) - XCTFail("Unexpected succeded!") - } catch let error as DecodingError { - guard case .dataCorrupted(let context) = error else { - XCTFail("Unexpected error!") - return - } - XCTAssertEqual(context.codingPath.map { $0.stringValue }, ["val", "bool"]) - } - } - - func testStructWithCorruptDataOnUnkeyedNestedContainer() throws { - struct TestWrapper: Codable, Equatable { - enum CodingKeys: String, CodingKey { - case nested - } - - enum NestedCodingKeys: String, CodingKey { - case bool - } - - let val: Test - - init(val: Test) { - self.val = val - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - var nestedContainer = try container.nestedUnkeyedContainer(forKey: .nested) - self.val = try nestedContainer.decode(Test.self) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - var nestedContainer = container.nestedUnkeyedContainer(forKey: .nested) - try nestedContainer.encode(val) - } - } - struct Test: Codable, Equatable { - let bool: CorruptBool - } - - let corruptedBytes: [UInt8] = [ - 0b00111010, 118, 97, 108, // String key 'val', varint - 15, // Byte length of val value - 0b01101010, 110, 101, 115, 116, 101, 100, // String key 'nested', varint - 7, // Byte length of nested value - 6, // Byte length of unkeyed container - 0b01001000, 98, 111, 111, 108, // String key 'bool', varint - 0 // Corrupt! - ] - - let data = try BinaryEncoder().encode(KeyedBox(TestWrapper(val: Test(bool: false)))) - XCTAssertEqual(Array(data), corruptedBytes) - - do { - let _ = try BinaryDecoder().decode(KeyedBox.self, from: data) - XCTFail("Unexpected succeded!") - } catch let error as DecodingError { - guard case .dataCorrupted(let context) = error else { - XCTFail("Unexpected error!") - return - } - XCTAssertEqual(context.codingPath.map { $0.stringValue }, ["val", "0", "bool"]) - } - } - - func testOptionalStructWithMissingValueAndWrongLengths() throws { - struct Test: Codable, Equatable { - let bool: Bool - } - - let corruptedBytes: [UInt8] = [ - 0b00111010, 118, 97, 108, // String key 'val', varint - 8, // Supposed byte length of optional val - 1, // 1 optional, - 6, // Supposed byte length of wrapped val - 0b01001000, 98, 111, 111, 108, // String key 'bool', varint - // No value! - ] - - let box: KeyedBox = KeyedBox(Test(bool: true)) - - var data = try BinaryEncoder().encode(box) - data.removeLast() - XCTAssertEqual(Array(data), corruptedBytes) - - do { - let _ = try BinaryDecoder().decode(KeyedBox.self, from: data) - XCTFail("Unexpected succeded!") - } catch let error as DecodingError { - guard case .dataCorrupted(let context) = error else { - XCTFail("Unexpected error!") - return - } - XCTAssertEqual(context.codingPath.map { $0.stringValue }, ["val"]) - } - } - - func testOptionalStructWithMissingValueAndWrongNestedLength() throws { - struct Test: Codable, Equatable { - let bool: Bool - } - - let corruptedBytes: [UInt8] = [ - 0b00111010, 118, 97, 108, // String key 'val', varint - 7, // Byte length of optional val (modified!) - 1, // 1 as in the optional is present, - 6, // Supposed byte length of wrapped val - 0b01001000, 98, 111, 111, 108, // String key 'bool', varint - // No value! - ] - - let box: KeyedBox = KeyedBox(Test(bool: true)) - - var data = try BinaryEncoder().encode(box) - data[4] -= 1 - data.removeLast() - XCTAssertEqual(Array(data), corruptedBytes) - - do { - let _ = try BinaryDecoder().decode(KeyedBox.self, from: data) - XCTFail("Unexpected succeded!") - } catch let error as DecodingError { - guard case .dataCorrupted(let context) = error else { - XCTFail("Unexpected error!") - return - } - XCTAssertEqual(context.codingPath.map { $0.stringValue }, ["val"]) - } - } - - func testOptionalStructWithMissingValueButCorrectByteLengths() throws { - struct Test: Codable, Equatable { - let bool: Bool - } - - let corruptedBytes: [UInt8] = [ - 0b00111010, 118, 97, 108, // String key 'val', varint - 7, // Byte length of optional val (modified!) - 1, // 1 as in the optional is present, - 5, // Byte length of wrapped val (modified!) - 0b01001000, 98, 111, 111, 108, // String key 'bool', varint - // No value! - ] - - let box: KeyedBox = KeyedBox(Test(bool: true)) - - var data = try BinaryEncoder().encode(box) - data[4] -= 1 - data[6] -= 1 - data.removeLast() - XCTAssertEqual(Array(data), corruptedBytes) - - do { - let _ = try BinaryDecoder().decode(KeyedBox.self, from: data) - XCTFail("Unexpected succeded!") - } catch let error as DecodingError { - guard case .dataCorrupted(let context) = error else { - XCTFail("Unexpected error!") - return - } - XCTAssertEqual(context.codingPath.map { $0.stringValue }, ["val", "bool"]) + GenericTestStruct.decode { decoder in + XCTAssertEqual(decoder.codingPath, []) + let container = try decoder.container(keyedBy: SomeKey.self) + let unkeyed = try container.nestedUnkeyedContainer(forKey: .value) + XCTAssertEqual(unkeyed.codingPath, [1]) } + try compare(GenericTestStruct()) } } diff --git a/Tests/BinaryCodableTests/CustomDecodingTests.swift b/Tests/BinaryCodableTests/CustomDecodingTests.swift index d62f2bf..1491045 100644 --- a/Tests/BinaryCodableTests/CustomDecodingTests.swift +++ b/Tests/BinaryCodableTests/CustomDecodingTests.swift @@ -36,7 +36,7 @@ extension Timestamped: Decodable where Value: Decodable { } extension Timestamped: Equatable where Value: Equatable { - + } /// A special semantic version with a fourth number @@ -101,8 +101,6 @@ extension Version: Decodable { } extension Version: Encodable { } extension Version: Equatable { } - - final class CustomDecodingTests: XCTestCase { func testCustomDecoding() throws { @@ -111,7 +109,7 @@ final class CustomDecodingTests: XCTestCase { let decoded = try BinaryDecoder().decode(Timestamped.self, from: encoded) XCTAssertEqual(value, decoded) } - + func testCustomVersionDecoding() throws { let version = Version(major: 1, minor: 2, patch: 3) let value = Timestamped(value: version) @@ -119,18 +117,22 @@ final class CustomDecodingTests: XCTestCase { let decoded = try BinaryDecoder().decode(Timestamped.self, from: encoded) XCTAssertEqual(version, decoded.value) } - + + /** + This function tests if a `RawRepresentable` can be decoded directly as `RawRepresentable.RawValue?` + */ func testDecodingAsDifferentType() throws { let version = Version(major: 1, minor: 2, patch: 3) let encoded = try BinaryEncoder().encode(version) - let decoded = try BinaryDecoder().decode(String.self, from: encoded) - let encoded2 = try BinaryEncoder().encode("1.2.3") - print(Array(encoded2)) - let decoded2 = try BinaryDecoder().decode(Version.self, from: encoded2) + let decoded = try BinaryDecoder().decode(String?.self, from: encoded) XCTAssertEqual(version.rawValue, decoded) + + let version2: String? = "1.2.3" + let encoded2 = try BinaryEncoder().encode(version2) + let decoded2 = try BinaryDecoder().decode(Version.self, from: encoded2) XCTAssertEqual(version, decoded2) } - + func testEncodingAsDifferentType() throws { let version = Version(major: 1, minor: 2, patch: 3) let time = Date() diff --git a/Tests/BinaryCodableTests/EnumEncodingTests.swift b/Tests/BinaryCodableTests/EnumEncodingTests.swift index 7cd38ca..45d6c27 100644 --- a/Tests/BinaryCodableTests/EnumEncodingTests.swift +++ b/Tests/BinaryCodableTests/EnumEncodingTests.swift @@ -9,14 +9,14 @@ final class EnumEncodingTests: XCTestCase { case two } let expected1: [UInt8] = [ - 0b00111010, /// `String` key, length `3` + 7, /// `String` key, length `3` 111, 110, 101, /// String key `one` 0 /// Encodes `0` as the `value` ] try compare(Test.one, to: expected1) let expected2: [UInt8] = [ - 0b00111010, /// `String` key, length `3` + 7, /// `String` key, length `3` 116, 119, 111, /// String key `one` 0 /// Encodes `0` as the `value` ] @@ -28,8 +28,8 @@ final class EnumEncodingTests: XCTestCase { case one = 1 case two = 2 } - try compare(Test.one, to: [2]) - try compare(Test.two, to: [4]) + try compare(Test.one, to: [0, 2]) // Nil indicator + raw value + try compare(Test.two, to: [0, 4]) } func testStringEnumEncoding() throws { @@ -37,17 +37,17 @@ final class EnumEncodingTests: XCTestCase { case one = "one" case two = "two" } - try compare(Test.one, to: [111, 110, 101]) - try compare(Test.two, to: [116, 119, 111]) + try compare(Test.one, to: [0, 111, 110, 101]) + try compare(Test.two, to: [0, 116, 119, 111]) } - + func testEnumWithAssociatedValues() throws { /** Note: `Codable` encoded associated values using the string keys `_0`, `_1`, ... This depends on the number of associated values */ enum Test: Codable, Equatable { - + case one(String) case two(Int) case three(Data) @@ -55,85 +55,89 @@ final class EnumEncodingTests: XCTestCase { case five(UInt, Int8) } try compare(Test.one("Some"), to: [ - 0b00111010, // String key(3), VarLen + 7, // String key, length 3 111, 110, 101, // String "one" - 8, // Length 8 - 42, // String key(2), VarLen + 16, // Length 8 + 5, // String key, length 2 95, 48, // String "_0" - 4, // Length 4 + 8, // Length 4 83, 111, 109, 101 // String "Some" ]) try compare(Test.two(123), to: [ - 0b00111010, // String key(3), VarLen + 7, // String key, length 3 116, 119, 111, // String "two" - 5, // Length 5 - 40, // String key(2), VarInt + 12, // Length 6 + 5, // String key, length 2 95, 48, // String "_0" + 4, // Length 2 246, 1 // Int(123) ]) try compare(Test.three(.init(repeating: 42, count: 3)), to: [ - 0b01011010, // String key(5), VarLen + 11, // String key, length 5 116, 104, 114, 101, 101, // String "three" - 7, // Length 7 - 42, // String key, VarLen, length 2 + 14, // Length 7 + 5, // String key, length 2 95, 48, // String "_0" - 3, // Length 2 + 6, // Length 3 42, 42, 42 // Data(42, 42, 42) ]) - + try compare(Test.four(true), to: [ - 0b01001010, // String key(4), VarLen + 9, // String key, length 4 102, 111, 117, 114, // String "four" - 4, // Length 4 - 40, // String key(2), VarInt + 10, // Length 5 + 5, // String key, length 2 95, 48, // String "_0" - 1 // Bool(true) + 2, 1 // Bool(true) ]) - + let start: [UInt8] = [ - 0b01001010, // String key(4), VarLen + 9, // String key, length 4 102, 105, 118, 101, // String "five" - 8] // Length 8 + 20] // Length 10 let a: [UInt8] = [ - 46, // String key(2), Byte - 95, 49, // String "_1" - 133] // Int8(-123) - let b: [UInt8] = [ - 40, // String key(2), VarInt + 5, // String key, length 2 95, 48, // String "_0" + 2, // Length 1 123] // UInt(123) - try compare(Test.five(123, -123), possibleResults: [start + a + b, start + b + a]) - + let b: [UInt8] = [ + 5, // String key, length 2 + 95, 49, // String "_1" + 2, // Length 1 + 133] // Int8(-123) + try compare(Test.five(123, -123), toOneOf: [start + a + b, start + b + a]) + struct Wrap: Codable, Equatable { - + let value: Test - + enum CodingKeys: Int, CodingKey { case value = 1 } } - + try compare(Wrap(value: .four(true)), to: [ - 0b00010010, // Int key(1), VarLen - 10, // Length 10 - 0b01001010, // String key, VarLen, length 4 + 2, // Int key '1' + 22, // Length 11 + 9, // String key, length 4 102, 111, 117, 114, // String "four" - 4, // Length 4 - 40, // String key(2), VarInt + 10, // Length 5 + 5, // String key, length 2 95, 48, // String "_0" + 2, // Length 1 1 // Bool(true) ]) } - + func testEnumWithAssociatedValuesAndIntegerKeys() throws { enum Test: Codable, Equatable { - + case one(String) case two(Int) case three(Data) case four(Bool) case five(UInt, Int8) - + enum CodingKeys: Int, CodingKey { case one = 1 case two = 2 @@ -142,65 +146,72 @@ final class EnumEncodingTests: XCTestCase { case five = 5 } } - + try compare(Test.one("Some"), to: [ - 0b00010010, // Int key(1), VarLen - 8, // Length 8 - 42, // String key(2), VarLen + 2, // Int key 1 + 16, // Length 8 + 5, // String key, length 2 95, 48, // String "_0" - 4, // Length 4 + 8, // Length 4 83, 111, 109, 101 // String "Some" ]) + try compare(Test.two(123), to: [ - 0b00100010, // Int key(2), VarLen - 5, // Length 5 - 40, // String key(2), VarInt + 4, // Int key 2 + 12, // Length 6 + 5, // String key, length 2 95, 48, // String "_0" + 4, // Length 2 246, 1 // Int(123) ]) + try compare(Test.three(.init(repeating: 42, count: 3)), to: [ - 0b00110010, // Int key(3), VarLen - 7, // Length 7 - 42, // String key, VarLen, length 2 + 6, // Int key 3 + 14, // Length 7 + 5, // String key, length 2 95, 48, // String "_0" - 3, // Length 2 + 6, // Length 3 42, 42, 42 // Data(42, 42, 42) ]) - + try compare(Test.four(true), to: [ - 0b01000010, // Int key(4), VarLen - 4, // Length 4 - 40, // String key(2), VarInt + 8, // Int key 4 + 10, // Length 5 + 5, // String key, length 2 95, 48, // String "_0" + 2, // Length 1 1 // Bool(true) ]) - + let start: [UInt8] = [ - 0b01010010, // Int key(5), VarLen - 8] // Length 8 + 10, // Int key 5 + 20] // Length 10 let a: [UInt8] = [ - 46, // String key(2), Byte - 95, 49, // String "_1" - 133] // Int8(-123) - let b: [UInt8] = [ - 40, // String key(2), VarInt + 5, // String key, length 2 95, 48, // String "_0" + 2, // Length 1 123] // UInt(123) - try compare(Test.five(123, -123), possibleResults: [start + a + b, start + b + a]) + let b: [UInt8] = [ + 5, // String key, length 2 + 95, 49, // String "_1" + 2, // Length 1 + 133] // Int8(-123) + try compare(Test.five(123, -123), toOneOf: [start + a + b, start + b + a]) } - + func testDecodeUnknownCase() throws { enum Test: Int, Codable { case one // No raw value assigns 0 } - - try compare(Test.one, to: [0]) - + + try compare(Test.one, to: [0, 0]) // Nil indicator + RawValue 0 + let decoder = BinaryDecoder() do { - _ = try decoder.decode(Test.self, from: Data([1])) + _ = try decoder.decode(Test.self, from: Data([0, 1])) XCTFail("Enum decoding should fail for unknown case") - } catch DecodingError.dataCorrupted(_) { + } catch DecodingError.dataCorrupted(let context) { + XCTAssertEqual(context.codingPath, []) // Correct error } } diff --git a/Tests/BinaryCodableTests/FileDecodingTests.swift b/Tests/BinaryCodableTests/FileDecodingTests.swift index 5389cab..b1dafd6 100644 --- a/Tests/BinaryCodableTests/FileDecodingTests.swift +++ b/Tests/BinaryCodableTests/FileDecodingTests.swift @@ -55,7 +55,7 @@ final class FileDecodingTests: XCTestCase { decoded.append(element) } try decoder.close() - + XCTAssertEqual(input, decoded) } diff --git a/Tests/BinaryCodableTests/Helper.swift b/Tests/BinaryCodableTests/Helper.swift index 1a8517d..4855bbd 100644 --- a/Tests/BinaryCodableTests/Helper.swift +++ b/Tests/BinaryCodableTests/Helper.swift @@ -1,32 +1,75 @@ +import Foundation import XCTest -import BinaryCodable +@testable import BinaryCodable -func compareEncoding(_ type: T.Type, value: T, to expected: [UInt8], sort: Bool = false) throws where T: Codable, T: Equatable { - let encoder = BinaryEncoder() - encoder.sortKeysDuringEncoding = sort - let data = try encoder.encode(value) - XCTAssertEqual(Array(data), expected) +struct GenericTestStruct: Codable, Equatable { - let decoder = BinaryDecoder() - let decoded = try decoder.decode(T.self, from: data) - XCTAssertEqual(value, decoded) -} + init() { + + } + + init(from decoder: Decoder) throws { + try GenericTestStruct.decodingRoutine(decoder) + } -func compare(_ value: T, to expected: [UInt8], sort: Bool = false) throws where T: Codable, T: Equatable { - try compareEncoding(T.self, value: value, to: expected, sort: sort) + func encode(to encoder: Encoder) throws { + try GenericTestStruct.encodingRoutine(encoder) + } + + private static var encodingRoutine: (Encoder) throws -> Void = { _ in } + + private static var decodingRoutine: (Decoder) throws -> Void = { _ in } + + static func encode(_ block: @escaping (Encoder) throws -> Void) { + encodingRoutine = block + } + + static func decode(_ block: @escaping (Decoder) throws -> Void) { + decodingRoutine = block + } } -func compareArray(_ type: T.Type, values: [T], to expected: [UInt8]) throws where T: Codable, T: Equatable { - try compare(values, to: expected) +func XCTAssertEqual(_ path: [CodingKey], _ other: [DecodingKey]) { + let convertedPath = path.map { DecodingKey(key: $0) } + XCTAssertEqual(convertedPath, other) } -func compare(_ value: T, possibleResults: [[UInt8]]) throws where T: Codable, T: Equatable { - let encoder = BinaryEncoder() - let data = try encoder.encode(value) - if !possibleResults.contains(Array(data)) { - XCTFail("\(Array(data)) is not one of the provided options: \(possibleResults)") + +extension XCTestCase { + + func compare(_ value: T, of type: T.Type = T.self, toOneOf possibleEncodings: [[UInt8]]) throws where T: Codable, T: Equatable { + let encoder = BinaryEncoder() + let data = try encoder.encode(value) + let bytes = Array(data) + if !possibleEncodings.contains(bytes) { + XCTFail("Encoded data is: \(bytes), allowed options: \(possibleEncodings)") + } + let decoder = BinaryDecoder() + let decoded = try decoder.decode(T.self, from: data) + XCTAssertEqual(decoded, value) + } + + func compare(_ value: T, of type: T.Type = T.self, to expected: [UInt8]? = nil, sortingKeys: Bool = false) throws where T: Codable, T: Equatable { + var encoder = BinaryEncoder() + if sortingKeys { + encoder.sortKeysDuringEncoding = true + } + let data = try encoder.encode(value) + if let expected { + XCTAssertEqual(Array(data), expected) + } else { + print("Encoded data: \(Array(data))") + } + + let decoder = BinaryDecoder() + let decoded = try decoder.decode(T.self, from: data) + XCTAssertEqual(value, decoded) + } + + func compareEncoding(of value: T, withType type: T.Type = T.self, isEqualTo expected: [UInt8]) throws where T: EncodablePrimitive, T: DecodablePrimitive, T: Equatable { + let encoded = value.encodedData + XCTAssertEqual(Array(encoded), expected) + let decoded = try T.init(data: encoded, codingPath: []) + XCTAssertEqual(decoded, value) } - let decoder = BinaryDecoder() - let decoded = try decoder.decode(T.self, from: data) - XCTAssertEqual(value, decoded) } diff --git a/Tests/BinaryCodableTests/KeyedEncodingTests.swift b/Tests/BinaryCodableTests/KeyedEncodingTests.swift index ab30101..9a9f2a1 100644 --- a/Tests/BinaryCodableTests/KeyedEncodingTests.swift +++ b/Tests/BinaryCodableTests/KeyedEncodingTests.swift @@ -1,55 +1,64 @@ import XCTest -import BinaryCodable +@testable import BinaryCodable + +private struct Test: Codable, Equatable where T: Codable, T: Equatable { + let value: T +} final class KeyedEncodingTests: XCTestCase { func testEncodingWithVarintType() throws { - struct Test: Codable, Equatable { - let value: Int - } - let expected: [UInt8] = [0b01011000, 118, 97, 108, 117, 101, 246, 1] + let expected: [UInt8] = [ + 11, // String key, length 5 + 118, 97, 108, 117, 101, // String "value" + 4, // length 2 + 246, 1] // Int 123 try compare(Test(value: 123), to: expected) } func testEncodingWithByteType() throws { - struct Test: Codable, Equatable { - let value: UInt8 - } - let expected: [UInt8] = [0b01011110, 118, 97, 108, 117, 101, 1] - try compare(Test(value: 1), to: expected) + let expected: [UInt8] = [ + 11, // String key, length 5 + 118, 97, 108, 117, 101, // String "value" + 2, // length 1 + 1] // UInt8(1) + try compare(Test(value: 1), to: expected) } func testEncodingWithTwoByteType() throws { - struct Test: Codable, Equatable { - let value: Int16 - } - let expected: [UInt8] = [0b01011111, 118, 97, 108, 117, 101, 0xD2, 0x04] - try compare(Test(value: 1234), to: expected) + let expected: [UInt8] = [ + 11, // String key, length 5 + 118, 97, 108, 117, 101, // String "value" + 4, // length 2 + 0xD2, 0x04] // 1234 + try compare(Test(value: 1234), to: expected) } func testEncodingWithVariableLengthType() throws { - struct Test: Codable, Equatable { - let value: String - } - let expected: [UInt8] = [0b01011010, 118, 97, 108, 117, 101, 5, 118, 97, 108, 117, 101] - try compare(Test(value: "value"), to: expected) + let expected: [UInt8] = [ + 11, // String key, length 5 + 118, 97, 108, 117, 101, // String "value" + 10, // length 5 + 118, 97, 108, 117, 101] + try compare(Test(value: "value"), to: expected) } func testEncodingWithFourByteType() throws { - struct Test: Codable, Equatable { - let value: Float - } - let expected: [UInt8] = [0b01011101, 118, 97, 108, 117, 101, 0x40, 0x49, 0x0F, 0xDA] - try compare(Test(value: .pi), to: expected) + let expected: [UInt8] = [ + 11, // String key, length 5 + 118, 97, 108, 117, 101, // String "value" + 8, // length 4 + 0x40, 0x49, 0x0F, 0xDA] + try compare(Test(value: .pi), to: expected) } func testEncodingWithEightByteType() throws { - struct Test: Codable, Equatable { - let value: Double - } - let expected: [UInt8] = [0b01011001, 118, 97, 108, 117, 101, - 0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18] - try compare(Test(value: -.pi), to: expected) + let expected: [UInt8] = [ + 11, // String key, length 5 + 118, 97, 108, 117, 101, // String "value" + 16, // length 8 + 0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18] + try compare(Test(value: -.pi), to: expected) } func testStructEncodingIntegerKey() throws { @@ -60,7 +69,10 @@ final class KeyedEncodingTests: XCTestCase { case value = 5 } } - let expected: [UInt8] = [0b01010111, 123, 0] + let expected: [UInt8] = [ + 10, // Int key 5 + 4, // length 2 + 123, 0] try compare(Test(value: 123), to: expected) } @@ -72,60 +84,135 @@ final class KeyedEncodingTests: XCTestCase { case value = 5318273 } } - let expected: [UInt8] = [0b10010111, 0xD0, 0xC9, 0x28, 123, 0] + let expected: [UInt8] = [ + 130, 154, 137, 5, // Int key 5318273 + 4, // length 2 + 123, 0] try compare(Test(value: 123), to: expected) } func testStringDictEncoding() throws { // Dictionaries with string keys are treated as keyed containers let value: [String : UInt8] = ["val": 123, "more": 124] - let part1: [UInt8] = [0b00111110, 118, 97, 108, 123] - let part2: [UInt8] = [0b01001110, 109, 111, 114, 101, 124] - try compare(value, possibleResults: [part1 + part2, part2 + part1]) + let part1: [UInt8] = [ + 7, // String key, length 3 + 118, 97, 108, // String "val" + 2, // Length 1 + 123] // 123 + let part2: [UInt8] = [ + 9, // String key, length 4 + 109, 111, 114, 101, // String "more" + 2, // Length 1 + 124] + try compare(value, toOneOf: [part1 + part2, part2 + part1]) } - + func testSortingStringDictEncoding() throws { // Dictionaries with string keys are treated as keyed containers let value: [String : UInt8] = ["val": 123, "more": 124] - try compare(value, to: [ - 0b01001110, 109, 111, 114, 101, 124, // More - 0b00111110, 118, 97, 108, 123, // Val - ], sort: true) + let part1: [UInt8] = [ + 7, // String key, length 3 + 118, 97, 108, // String "val" + 2, // Length 1 + 123] // 123 + let part2: [UInt8] = [ + 9, // String key, length 4 + 109, 111, 114, 101, // String "more" + 2, // Length 1 + 124] + try compare(value, to: part2 + part1, sortingKeys: true) } - + func testMixedStringIntegerKeyedDictionary() throws { // Strings which can be converted to integers are encoded as such // So the string "0" is actually encoded as Int key(0) let value: [String : UInt8] = ["val": 123, "0": 124] - let part1: [UInt8] = [0b00111110, 118, 97, 108, 123] // "Val" - let part2: [UInt8] = [0b00000110, 124] // Int key(0), UInt8(123) - try compare(value, possibleResults: [part1 + part2, part2 + part1]) + let part1: [UInt8] = [ + 7, // String key, length 3 + 118, 97, 108, // String "val" + 2, // Length 1 + 123] // 123 + let part2: [UInt8] = [ + 0, // Int key 0 + 2, // Length 1 + 124] // 124 + try compare(value, toOneOf: [part1 + part2, part2 + part1]) } func testIntDictEncoding() throws { // Dictionaries with int keys are treated as keyed containers let value: [Int : UInt8] = [123: 123, 124: 124] - let part1: [UInt8] = [0b10110110, 0x0F, 123] - let part2: [UInt8] = [0b11000110, 0x0F, 124] - try compare(value, possibleResults: [part1 + part2, part2 + part1]) + let part1: [UInt8] = [ + 246, 1, // Int key 123 + 2, // Length 1 + 123] + let part2: [UInt8] = [ + 248, 1, // Int key 124 + 2, // Length 1 + 124] + try compare(value, toOneOf: [part1 + part2, part2 + part1]) } - + func testSortingIntDictEncoding() throws { // Dictionaries with int keys are treated as keyed containers let value: [Int : UInt8] = [123: 123, 124: 124, 125: 125, 126: 126] try compare(value, to: [ - 0b10110110, 0x0F, 123, - 0b11000110, 0x0F, 124, - 0b11010110, 0x0F, 125, - 0b11100110, 0x0F, 126 - ], sort: true) + 246, 1, 2, 123, + 248, 1, 2, 124, + 250, 1, 2, 125, + 252, 1, 2, 126 + ], sortingKeys: true) } func testUIntDictEncoding() throws { // Other dictionaries (keys not int/string) are treated as unkeyed containers of key-value pairs let value: [UInt : UInt8] = [123: 123, 124: 124] - let part1: [UInt8] = [123, 123] - let part2: [UInt8] = [124, 124] - try compare(value, possibleResults: [part1 + part2, part2 + part1]) + let part1: [UInt8] = [ + 2, // Length 1 + 123, + 2, // Length 1 + 123 + ] + let part2: [UInt8] = [ + 2, // Length 1 + 124, + 2, // Length 1 + 124 + ] + try compare(value, toOneOf: [part1 + part2, part2 + part1]) + } + + func testExplicitlyEncodeNil() throws { + enum Keys: Int, CodingKey { + case value = 1 + case opt = 2 + } + GenericTestStruct.encode { encoder in + var container = encoder.container(keyedBy: Keys.self) + let value: String? = nil + try container.encodeIfPresent(value, forKey: .value) + try container.encodeNil(forKey: .opt) + } + GenericTestStruct.decode { decoder in + let container = try decoder.container(keyedBy: Keys.self) + XCTAssertFalse(container.contains(.value)) + // Nil is not encoded + XCTAssertFalse(container.contains(.opt)) + + let s = try container.decodeIfPresent(String.self, forKey: .value) + XCTAssertEqual(s, nil) + + let optIsNil = try container.decodeNil(forKey: .opt) + XCTAssertTrue(optIsNil) + let opt = try container.decodeIfPresent(Bool.self, forKey: .opt) + XCTAssertNil(opt) + do { + _ = try container.decode(Bool.self, forKey: .opt) + XCTFail() + } catch { + + } + } + try compare(GenericTestStruct(), to: []) } } diff --git a/Tests/BinaryCodableTests/OptionalEncodingTests.swift b/Tests/BinaryCodableTests/OptionalEncodingTests.swift index 2d85645..31a6124 100644 --- a/Tests/BinaryCodableTests/OptionalEncodingTests.swift +++ b/Tests/BinaryCodableTests/OptionalEncodingTests.swift @@ -3,31 +3,40 @@ import BinaryCodable final class OptionalEncodingTests: XCTestCase { - func testSingleOptional() throws { + func testArrayOfOptionals() throws { let value: [Int?] = [1, nil] - try compare(value, to: [1, 2, 0]) + try compare(value, to: [ + 2, // Not nil, length 1 + 2, // Int 1 + 1 // Nil, length zero + ]) } - + func testOptionalBoolEncoding() throws { - try compareEncoding(Bool?.self, value: true, to: [1, 1]) - try compareEncoding(Bool?.self, value: false, to: [1, 0]) - try compareEncoding(Bool?.self, value: nil, to: [0]) + try compare(true, of: Bool?.self, to: [0, 1]) + try compare(false, of: Bool?.self, to: [0, 0]) + try compare(nil, of: Bool?.self, to: [1]) } func testDoubleOptionalBoolEncoding() throws { - try compareEncoding(Bool??.self, value: .some(.some(true)), to: [1, 1, 1]) - try compareEncoding(Bool??.self, value: .some(.some(false)), to: [1, 1, 0]) - try compareEncoding(Bool??.self, value: .some(.none), to: [1, 0]) - try compareEncoding(Bool??.self, value: .none, to: [0]) + try compare(.some(.some(true)), of: Bool??.self, to: [0, 0, 1]) + try compare(.some(.some(false)), of: Bool??.self, to: [0, 0, 0]) + try compare(.some(.none), of: Bool??.self, to: [0, 1]) + try compare(.none, of: Bool??.self, to: [1]) } func testOptionalStruct() throws { struct T: Codable, Equatable { var a: Int } - try compareEncoding(T?.self, value: T(a: 123), - to: [1, 4, 24, 97, 246, 1]) - try compareEncoding(T?.self, value: nil, to: [0]) + try compare(T(a: 123), of: T?.self, to: [ + 0, // Not nil + 3, // String key, length 1 + 97, // String "a" + 4, // Length 2 + 246, 1 // Int 123 + ]) + try compare(nil, of: T?.self, to: [1]) } func testOptionalInStructEncoding() throws { @@ -42,10 +51,36 @@ final class OptionalEncodingTests: XCTestCase { } } // Note: `encodeNil()` is not called for single optionals - try compare(Test(value: 123, opt: nil), to: [0b01010111, 123, 0]) - let part1: [UInt8] = [0b01010111, 123, 0] // value: 123 - let part2: [UInt8] = [0b01000111, 12, 0] // opt: 12 - try compare(Test(value: 123, opt: 12), possibleResults: [part1 + part2, part2 + part1]) + try compare(Test(value: 123, opt: nil), to: [ + 10, // Int key 5 + 4, // Length 2 + 123, 0 // Int 123 + ]) + let part1: [UInt8] = [ + 10, // Int key 5 + 4, // Length 2 + 123, 0 // value: 123 + ] + let part2: [UInt8] = [ + 8, // Int key 4 + 4, // Length 2 + 12, 0 // opt: 12 + ] + try compare(Test(value: 123, opt: 12), toOneOf: [part1 + part2, part2 + part1]) + } + + func testDoubleOptional() throws { + struct Test: Codable, Equatable { + let opt: Int16?? + + enum CodingKeys: Int, CodingKey { + case opt = 4 + } + } + try compare(Test(opt: .some(nil)), to: [ + 8, // Int key 4 + 1, // nil + ]) } func testDoubleOptionalInStruct() throws { @@ -55,22 +90,94 @@ final class OptionalEncodingTests: XCTestCase { let opt: Int16?? enum CodingKeys: Int, CodingKey { - case value = 4 - case opt = 5 + case value = 5 + case opt = 4 } } - try compare(Test(value: 123, opt: nil), to: [0b01000111, 123, 0]) - let part1: [UInt8] = [0b01000111, 123, 0] - let part2: [UInt8] = [0b01010010, 3, 1, 12, 0] // Optionals are VarLen - try compare(Test(value: 123, opt: 12), possibleResults: [part1 + part2, part2 + part1]) + try compare(Test(value: 123, opt: nil), to: [ + 10, // Int key 5 + 4, // Length 2 + 123, 0 // Int 123 + ]) + + let part1: [UInt8] = [ + 10, // Int key 5 + 4, // Length 2 + 123, 0 // value: 123 + ] + let part2: [UInt8] = [ + 8, // Int key 4 + 1, // nil + ] + try compare(Test(value: 123, opt: .some(nil)), toOneOf: [part1 + part2, part2 + part1]) + + let part3: [UInt8] = [ + 10, // Int key 5 + 4, // Length 2 + 123, 0 // value: 123 + ] + let part4: [UInt8] = [ + 8, // Int key 4 + 4, // Not nil, Length 2 + 12, 0 // value: 12 + ] + try compare(Test(value: 123, opt: 12), toOneOf: [part3 + part4, part4 + part3]) } + + func testTripleOptionalInStruct() throws { + struct Test: Codable, Equatable { + let opt: Int??? - func testClassWithOptionalProperty() throws { - let item = TestClass(withName: "Bob", endDate: nil) - try compare(item, to: [18, 3, 66, 111, 98], sort: true) + enum CodingKeys: Int, CodingKey { + case opt = 4 + } + } + try compare(Test(opt: nil), to: []) + + try compare(Test(opt: .some(nil)), to: [ + 8, // Int key 4 + 1, // nil + ]) + + try compare(Test(opt: .some(.some(nil))), to: [ + 8, // Int key 4 + 2, // Not nil, length 2 + 1, // nil + ]) + + try compare(Test(opt: .some(.some(.some(5)))), to: [ + 8, // Int key 4 + 4, // Not nil, length 2 + 0, // Not nil + 10, // Int 10 + ]) + } - let item2 = TestClass(withName: "Bob", endDate: "s") - try compare(item2, to: [18, 3, 66, 111, 98, 34, 3, 1, 1, 115], sort: true) + func testClassWithOptionalProperty() throws { + // NOTE: Here, the field for 'date' is present in the data + // because the optional is directly encoded using encode() + // The field for 'partner' is not added, since it's encoded using `encodeIfPresent()` + let item = TestClass(name: "Bob", date: nil, partner: nil) + try compare(item, to: [ + 2, // Int key 1 + 6, // Length 3 + 66, 111, 98, // Bob + 4, // Int key 2 + 1 // Nil + ], sortingKeys: true) + + let item2 = TestClass(name: "Bob", date: "s", partner: "Alice") + try compare(item2, to: [ + 2, // Int key 1 + 6, // Length 3 + 66, 111, 98, // Bob + 4, // Int key 2 + 2, // Length 2 + 115, // "s" + 6, // Int key 3 + 10, // Length 5 + 65, 108, 105, 99, 101 + ], sortingKeys: true) } } @@ -78,35 +185,40 @@ final class OptionalEncodingTests: XCTestCase { private final class TestClass: Codable, Equatable, CustomStringConvertible { let name: String let date: String? + let partner: String? enum CodingKeys: Int, CodingKey { case name = 1 case date = 2 + case partner = 3 } convenience init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let name = try container.decode(String.self, forKey: .name) let date = try container.decode(String?.self, forKey: .date) - self.init(withName: name, endDate: date) + let partner = try container.decodeIfPresent(String.self, forKey: .partner) + self.init(name: name, date: date, partner: partner) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(name, forKey: .name) try container.encode(date, forKey: .date) + try container.encodeIfPresent(partner, forKey: .partner) } - init(withName name: String, endDate: String?) { + init(name: String, date: String?, partner: String?) { self.name = name - date = endDate + self.date = date + self.partner = partner } static func == (lhs: TestClass, rhs: TestClass) -> Bool { - lhs.name == rhs.name && lhs.date == rhs.date + lhs.name == rhs.name && lhs.date == rhs.date && lhs.partner == rhs.partner } var description: String { - "\(name): \(date ?? "nil")" + "\(name): \(date ?? "nil"), \(partner ?? "nil")" } } diff --git a/Tests/BinaryCodableTests/PrimitiveEncodingTests.swift b/Tests/BinaryCodableTests/PrimitiveEncodingTests.swift index dc8b410..69fda0b 100644 --- a/Tests/BinaryCodableTests/PrimitiveEncodingTests.swift +++ b/Tests/BinaryCodableTests/PrimitiveEncodingTests.swift @@ -1,179 +1,134 @@ import XCTest -import BinaryCodable +@testable import BinaryCodable final class PrimitiveEncodingTests: XCTestCase { - + func testBoolEncoding() throws { - func compare(_ value: Bool, to expected: [UInt8]) throws { - try compareEncoding(Bool.self, value: value, to: expected) - } - try compare(true, to: [1]) - try compare(false, to: [0]) + try compareEncoding(of: true, isEqualTo: [1]) + try compareEncoding(of: false, isEqualTo: [0]) } - + func testInt8Encoding() throws { - func compare(_ value: Int8, to expected: [UInt8]) throws { - try compareEncoding(Int8.self, value: value, to: expected) - } - try compare(.zero, to: [0]) - try compare(123, to: [123]) - try compare(.min, to: [128]) - try compare(.max, to: [127]) - try compare(-1, to: [255]) + try compareEncoding(of: .zero, withType: Int8.self, isEqualTo: [0]) + try compareEncoding(of: 123, withType: Int8.self, isEqualTo: [123]) + try compareEncoding(of: .min, withType: Int8.self, isEqualTo: [128]) + try compareEncoding(of: .max, withType: Int8.self, isEqualTo: [127]) + try compareEncoding(of: -1, withType: Int8.self, isEqualTo: [255]) } - + func testInt16Encoding() throws { - func compare(_ value: Int16, to expected: [UInt8]) throws { - try compareEncoding(Int16.self, value: value, to: expected) - } - try compare(.zero, to: [0, 0]) - try compare(123, to: [123, 0]) - try compare(.min, to: [0, 128]) - try compare(.max, to: [255, 127]) - try compare(-1, to: [255, 255]) + try compareEncoding(of: .zero, withType: Int16.self, isEqualTo: [0, 0]) + try compareEncoding(of: 123, withType: Int16.self, isEqualTo: [123, 0]) + try compareEncoding(of: .min, withType: Int16.self, isEqualTo: [0, 128]) + try compareEncoding(of: .max, withType: Int16.self, isEqualTo: [255, 127]) + try compareEncoding(of: -1, withType: Int16.self, isEqualTo: [255, 255]) } - + func testInt32Encoding() throws { - func compare(_ value: Int32, to expected: [UInt8]) throws { - try compareEncoding(Int32.self, value: value, to: expected) - } - try compare(.zero, to: [0]) - try compare(-1, to: [1]) - try compare(1, to: [2]) - try compare(-2, to: [3]) - try compare(123, to: [246, 1]) + try compareEncoding(of: .zero, withType: Int32.self, isEqualTo: [0]) + try compareEncoding(of: -1, withType: Int32.self, isEqualTo: [1]) + try compareEncoding(of: 1, withType: Int32.self, isEqualTo: [2]) + try compareEncoding(of: -2, withType: Int32.self, isEqualTo: [3]) + try compareEncoding(of: 123, withType: Int32.self, isEqualTo: [246, 1]) /// Min is: `-2147483648`, encoded as `4294967295` - try compare(.min, to: [255, 255, 255, 255, 15]) // The last byte contains 4 bits of data + try compareEncoding(of: .min, withType: Int32.self, isEqualTo: [255, 255, 255, 255, 15]) // The last byte contains 4 bits of data /// Max is: `2147483647`, encoded as `4294967294` - try compare(.max, to: [254, 255, 255, 255, 15]) // The last byte contains 4 bits of data + try compareEncoding(of: .max, withType: Int32.self, isEqualTo: [254, 255, 255, 255, 15]) // The last byte contains 4 bits of data } - + func testInt64Encoding() throws { - func compare(_ value: Int64, to expected: [UInt8]) throws { - try compareEncoding(Int64.self, value: value, to: expected) - } - try compare(0, to: [0]) - try compare(123, to: [246, 1]) - try compare(.max, to: [0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) - try compare(.min, to: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) - try compare(-1, to: [1]) + try compareEncoding(of: 0, withType: Int64.self, isEqualTo: [0]) + try compareEncoding(of: 123, withType: Int64.self, isEqualTo: [246, 1]) + try compareEncoding(of: .max, withType: Int64.self, isEqualTo: [0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + try compareEncoding(of: .min, withType: Int64.self, isEqualTo: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + try compareEncoding(of: -1, withType: Int64.self, isEqualTo: [1]) } - + func testIntEncoding() throws { - func compare(_ value: Int, to expected: [UInt8]) throws { - try compareEncoding(Int.self, value: value, to: expected) - } - try compare(0, to: [0]) - try compare(123, to: [246, 1]) - try compare(.max, to: [0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) - try compare(.min, to: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) - try compare(-1, to: [1]) + try compareEncoding(of: 0, withType: Int.self, isEqualTo: [0]) + try compareEncoding(of: 123, withType: Int.self, isEqualTo: [246, 1]) + try compareEncoding(of: .max, withType: Int.self, isEqualTo: [0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + try compareEncoding(of: .min, withType: Int.self, isEqualTo: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + try compareEncoding(of: -1, withType: Int.self, isEqualTo: [1]) } - + func testUInt8Encoding() throws { - func compare(_ value: UInt8, to expected: [UInt8]) throws { - try compareEncoding(UInt8.self, value: value, to: expected) - } - try compare(.zero, to: [0]) - try compare(123, to: [123]) - try compare(.min, to: [0]) - try compare(.max, to: [255]) + try compareEncoding(of: .zero, withType: UInt8.self, isEqualTo: [0]) + try compareEncoding(of: 123, withType: UInt8.self, isEqualTo: [123]) + try compareEncoding(of: .min, withType: UInt8.self, isEqualTo: [0]) + try compareEncoding(of: .max, withType: UInt8.self, isEqualTo: [255]) } - + func testUInt16Encoding() throws { - func compare(_ value: UInt16, to expected: [UInt8]) throws { - try compareEncoding(UInt16.self, value: value, to: expected) - } - try compare(.zero, to: [0, 0]) - try compare(123, to: [123, 0]) - try compare(.min, to: [0, 0]) - try compare(.max, to: [255, 255]) - try compare(12345, to: [0x39, 0x30]) + try compareEncoding(of: .zero, withType: UInt16.self, isEqualTo: [0, 0]) + try compareEncoding(of: 123, withType: UInt16.self, isEqualTo: [123, 0]) + try compareEncoding(of: .min, withType: UInt16.self, isEqualTo: [0, 0]) + try compareEncoding(of: .max, withType: UInt16.self, isEqualTo: [255, 255]) + try compareEncoding(of: 12345, withType: UInt16.self, isEqualTo: [0x39, 0x30]) } - + func testUInt32Encoding() throws { - func compare(_ value: UInt32, to expected: [UInt8]) throws { - try compareEncoding(UInt32.self, value: value, to: expected) - } - try compare(.zero, to: [0]) - try compare(123, to: [123]) - try compare(.min, to: [0]) - try compare(12345, to: [0xB9, 0x60]) - try compare(123456, to: [0xC0, 0xC4, 0x07]) - try compare(12345678, to: [0xCE, 0xC2, 0xF1, 0x05]) - try compare(1234567890, to: [0xD2, 0x85, 0xD8, 0xCC, 0x04]) - try compare(.max, to: [255, 255, 255, 255, 15]) // The last byte contains 4 bits of data + try compareEncoding(of: .zero, withType: UInt32.self, isEqualTo: [0]) + try compareEncoding(of: 123, withType: UInt32.self, isEqualTo: [123]) + try compareEncoding(of: .min, withType: UInt32.self, isEqualTo: [0]) + try compareEncoding(of: 12345, withType: UInt32.self, isEqualTo: [0xB9, 0x60]) + try compareEncoding(of: 123456, withType: UInt32.self, isEqualTo: [0xC0, 0xC4, 0x07]) + try compareEncoding(of: 12345678, withType: UInt32.self, isEqualTo: [0xCE, 0xC2, 0xF1, 0x05]) + try compareEncoding(of: 1234567890, withType: UInt32.self, isEqualTo: [0xD2, 0x85, 0xD8, 0xCC, 0x04]) + try compareEncoding(of: .max, withType: UInt32.self, isEqualTo: [255, 255, 255, 255, 15]) // The last byte contains 4 bits of data } - + func testUInt64Encoding() throws { - func compare(_ value: UInt64, to expected: [UInt8]) throws { - try compareEncoding(UInt64.self, value: value, to: expected) - } - try compare(0, to: [0]) - try compare(123, to: [123]) - try compare(.min, to: [0]) - try compare(12345, to: [0xB9, 0x60]) - try compare(123456, to: [0xC0, 0xC4, 0x07]) - try compare(12345678, to: [0xCE, 0xC2, 0xF1, 0x05]) - try compare(1234567890, to: [0xD2, 0x85, 0xD8, 0xCC, 0x04]) - try compare(12345678901234, to: [0xF2, 0xDF, 0xB8, 0x9E, 0xA7, 0xE7, 0x02]) - try compare(.max, to: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + try compareEncoding(of: 0, withType: UInt64.self, isEqualTo: [0]) + try compareEncoding(of: 123, withType: UInt64.self, isEqualTo: [123]) + try compareEncoding(of: .min, withType: UInt64.self, isEqualTo: [0]) + try compareEncoding(of: 12345, withType: UInt64.self, isEqualTo: [0xB9, 0x60]) + try compareEncoding(of: 123456, withType: UInt64.self, isEqualTo: [0xC0, 0xC4, 0x07]) + try compareEncoding(of: 12345678, withType: UInt64.self, isEqualTo: [0xCE, 0xC2, 0xF1, 0x05]) + try compareEncoding(of: 1234567890, withType: UInt64.self, isEqualTo: [0xD2, 0x85, 0xD8, 0xCC, 0x04]) + try compareEncoding(of: 12345678901234, withType: UInt64.self, isEqualTo: [0xF2, 0xDF, 0xB8, 0x9E, 0xA7, 0xE7, 0x02]) + try compareEncoding(of: .max, withType: UInt64.self, isEqualTo: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) } - + func testUIntEncoding() throws { - func compare(_ value: UInt, to expected: [UInt8]) throws { - try compareEncoding(UInt.self, value: value, to: expected) - } - try compare(0, to: [0]) - try compare(123, to: [123]) - try compare(.min, to: [0]) - try compare(12345, to: [0xB9, 0x60]) - try compare(123456, to: [0xC0, 0xC4, 0x07]) - try compare(12345678, to: [0xCE, 0xC2, 0xF1, 0x05]) - try compare(1234567890, to: [0xD2, 0x85, 0xD8, 0xCC, 0x04]) - try compare(12345678901234, to: [0xF2, 0xDF, 0xB8, 0x9E, 0xA7, 0xE7, 0x02]) - try compare(.max, to: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + try compareEncoding(of: 0, withType: UInt.self, isEqualTo: [0]) + try compareEncoding(of: 123, withType: UInt.self, isEqualTo: [123]) + try compareEncoding(of: .min, withType: UInt.self, isEqualTo: [0]) + try compareEncoding(of: 12345, withType: UInt.self, isEqualTo: [0xB9, 0x60]) + try compareEncoding(of: 123456, withType: UInt.self, isEqualTo: [0xC0, 0xC4, 0x07]) + try compareEncoding(of: 12345678, withType: UInt.self, isEqualTo: [0xCE, 0xC2, 0xF1, 0x05]) + try compareEncoding(of: 1234567890, withType: UInt.self, isEqualTo: [0xD2, 0x85, 0xD8, 0xCC, 0x04]) + try compareEncoding(of: 12345678901234, withType: UInt.self, isEqualTo: [0xF2, 0xDF, 0xB8, 0x9E, 0xA7, 0xE7, 0x02]) + try compareEncoding(of: .max, withType: UInt.self, isEqualTo: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) } - + func testStringEncoding() throws { - func compare(_ value: String) throws { - try compareEncoding(String.self, value: value, to: Array(value.data(using: .utf8)!)) - } - try compare("Some") - try compare("A longer text with\n multiple lines") - try compare("More text") - try compare("eolqjwqu(Jan?!)§(!N") + try compare("Some", to: Array("Some".data(using: .utf8)!)) + try compare("A longer text with\n multiple lines", to: Array("A longer text with\n multiple lines".data(using: .utf8)!)) + try compare("More text", to: Array("More text".data(using: .utf8)!)) + try compare("eolqjwqu(Jan?!)§(!N", to: Array("eolqjwqu(Jan?!)§(!N".data(using: .utf8)!)) } - + func testFloatEncoding() throws { - func compare(_ value: Float, to expected: [UInt8]) throws { - try compareEncoding(Float.self, value: value, to: expected) - } - try compare(.greatestFiniteMagnitude, to: [0x7F, 0x7F, 0xFF, 0xFF]) - try compare(.zero, to: [0x00, 0x00, 0x00, 0x00]) - try compare(.pi, to: [0x40, 0x49, 0x0F, 0xDA]) - try compare(-.pi, to: [0xC0, 0x49, 0x0F, 0xDA]) - try compare(.leastNonzeroMagnitude, to: [0x00, 0x00, 0x00, 0x01]) + try compareEncoding(of: .greatestFiniteMagnitude, withType: Float.self, isEqualTo: [0x7F, 0x7F, 0xFF, 0xFF]) + try compareEncoding(of: .zero, withType: Float.self, isEqualTo: [0x00, 0x00, 0x00, 0x00]) + try compareEncoding(of: .pi, withType: Float.self, isEqualTo: [0x40, 0x49, 0x0F, 0xDA]) + try compareEncoding(of: -.pi, withType: Float.self, isEqualTo: [0xC0, 0x49, 0x0F, 0xDA]) + try compareEncoding(of: .leastNonzeroMagnitude, withType: Float.self, isEqualTo: [0x00, 0x00, 0x00, 0x01]) } - + func testDoubleEncoding() throws { - func compare(_ value: Double, to expected: [UInt8]) throws { - try compareEncoding(Double.self, value: value, to: expected) - } - try compare(.greatestFiniteMagnitude, to: [0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) - try compare(.zero, to: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) - try compare(.pi, to: [0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18]) - try compare(.leastNonzeroMagnitude, to: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]) - try compare(-.pi, to: [0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18]) + try compareEncoding(of: .greatestFiniteMagnitude, withType: Double.self, isEqualTo: [0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + try compareEncoding(of: .zero, withType: Double.self, isEqualTo: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + try compareEncoding(of: .pi, withType: Double.self, isEqualTo: [0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18]) + try compareEncoding(of: .leastNonzeroMagnitude, withType: Double.self, isEqualTo: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]) + try compareEncoding(of: -.pi, withType: Double.self, isEqualTo: [0xC0, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18]) } func testDataEncoding() throws { - func compare(_ value: Data, to expected: [UInt8]) throws { - try compareEncoding(Data.self, value: value, to: expected) - } - try compare(Data(), to: []) - try compare(Data([0]), to: [0]) - try compare(Data([0x40, 0x09, 0x21, 0xFB]), to: [0x40, 0x09, 0x21, 0xFB]) + try compareEncoding(of: Data(), withType: Data.self, isEqualTo: []) + try compareEncoding(of: Data([0]), withType: Data.self, isEqualTo: [0]) + try compareEncoding(of: Data([0x40, 0x09, 0x21, 0xFB]), withType: Data.self, isEqualTo: [0x40, 0x09, 0x21, 0xFB]) } } diff --git a/Tests/BinaryCodableTests/PropertyWrapperCodingTests.swift b/Tests/BinaryCodableTests/PropertyWrapperCodingTests.swift index 92daa9f..11cd82c 100644 --- a/Tests/BinaryCodableTests/PropertyWrapperCodingTests.swift +++ b/Tests/BinaryCodableTests/PropertyWrapperCodingTests.swift @@ -60,7 +60,8 @@ final class PropertyWrapperCodingTests: XCTestCase { line: UInt = #line ) throws { let bytePrefix: [UInt8] = [ - 0b01111010, 119, 114, 97, 112, 112, 101, 114, // String key 'wrapper', varint, + 15, // String key, length 7 + 119, 114, 97, 112, 112, 101, 114, // "wrapper" ] let wrapper = KeyedWrapper(wrapped) @@ -82,11 +83,11 @@ final class PropertyWrapperCodingTests: XCTestCase { encoding: WrappedString(val: "Some"), as: WrappedString?.self, expectByteSuffix: [ - 11, // Length 11 - 1, // 1 as in the optional is present - 9, // Length 9 - 0b00111010, 118, 97, 108, // String key 'val', varint - 4, // Length 4, + 20, // Length 10 + 0, // Non-nil + 7, // String key, length 3 + 118, 97, 108, // "val" + 8, // Length 4, 83, 111, 109, 101, // String "Some" ] ) @@ -97,8 +98,8 @@ final class PropertyWrapperCodingTests: XCTestCase { encoding: nil, as: WrappedString?.self, expectByteSuffix: [ - 1, // Length 1 - 0, // Optional is absent + 2, // Length 1 + 1, // Nil indicator ] ) } @@ -108,8 +109,8 @@ final class PropertyWrapperCodingTests: XCTestCase { encoding: .some(true), as: Bool?.self, expectByteSuffix: [ - 2, // Length 2 - 1, // Optional is present + 4, // Length 2 + 0, // Non-nil 1, // Boolean is true ] ) @@ -118,8 +119,8 @@ final class PropertyWrapperCodingTests: XCTestCase { encoding: .some(false), as: Bool?.self, expectByteSuffix: [ - 2, // Length 2 - 1, // Optional is present + 4, // Length 2 + 0, // Non-nil 0, // Boolean is false ] ) @@ -128,8 +129,8 @@ final class PropertyWrapperCodingTests: XCTestCase { encoding: nil, as: Bool?.self, expectByteSuffix: [ - 1, // Length 1 - 0, // Optional is present + 2, // Length 1 + 1, // Nil ] ) } @@ -138,25 +139,42 @@ final class PropertyWrapperCodingTests: XCTestCase { try assert( encoding: .some(.some(true)), as: Bool??.self, - expectByteSuffix: [3, 1, 1, 1] + expectByteSuffix: [ + 6, // Length 3 + 0, // Not nil + 0, // Not nil + 1 // true + ] ) try assert( encoding: .some(.some(false)), as: Bool??.self, - expectByteSuffix: [3, 1, 1, 0] + expectByteSuffix: [ + 6, // Length 3 + 0, // Not nil + 0, // Not nil + 0 // false + ] ) try assert( encoding: .some(nil), as: Bool??.self, - expectByteSuffix: [2, 1, 0] + expectByteSuffix: [ + 4, // Length 2 + 0, // Not nil + 1 // Nil + ] ) try assert( encoding: nil, as: Bool??.self, - expectByteSuffix: [1, 0] + expectByteSuffix: [ + 2, // Length 1 + 1 // Nil + ] ) } } diff --git a/Tests/BinaryCodableTests/Proto/TestTypes.pb.swift b/Tests/BinaryCodableTests/Proto/TestTypes.pb.swift deleted file mode 100644 index 0bd2d95..0000000 --- a/Tests/BinaryCodableTests/Proto/TestTypes.pb.swift +++ /dev/null @@ -1,795 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: TestTypes.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -import Foundation -import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct SimpleStruct { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Equivalent to Swift `Int64` - var integer64: Int64 = 0 - - /// Equivalent to Swift `String` - var text: String = String() - - /// Equivalent to Swift `Data` - var data: Data = Data() - - /// Equivalent to Swift `[UInt32]` - var intArray: [UInt32] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct WrappedContainer { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Equivalent to Swift `FixedSize` - var fourByteInt: Int32 = 0 - - /// Equivalent to Swift `FixedSize` - var fourByteUint: UInt32 = 0 - - /// Equivalent to Swift `FixedSize` - var eightByteInt: Int64 = 0 - - /// Equivalent to Swift `FixedSize` - var eightByteUint: UInt64 = 0 - - /// Equivalent to Swift `SignedValue` - var signed32: Int32 = 0 - - /// Equivalent to Swift `SignedValue` - var signed64: Int64 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Outer { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var inner: SimpleStruct { - get {return _inner ?? SimpleStruct()} - set {_inner = newValue} - } - /// Returns true if `inner` has been explicitly set. - var hasInner: Bool {return self._inner != nil} - /// Clears the value of `inner`. Subsequent reads from it will return its default value. - mutating func clearInner() {self._inner = nil} - - var more: SimpleStruct { - get {return _more ?? SimpleStruct()} - set {_more = newValue} - } - /// Returns true if `more` has been explicitly set. - var hasMore: Bool {return self._more != nil} - /// Clears the value of `more`. Subsequent reads from it will return its default value. - mutating func clearMore() {self._more = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _inner: SimpleStruct? = nil - fileprivate var _more: SimpleStruct? = nil -} - -struct Outer2 { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var values: [SimpleStruct] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// A container to test different map types -struct MapContainer { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var x: Dictionary = [:] - - var y: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct PrimitiveTypesContainer { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Swift equivalent: `Double` - var doubleValue: Double = 0 - - /// Swift equivalent: `Float` - var floatValue: Float = 0 - - /// Swift equivalent: `Int32` - var intValue32: Int32 = 0 - - /// Swift equivalent: `Int64` - var intValue64: Int64 = 0 - - /// Swift equivalent: `UInt32` - var uIntValue32: UInt32 = 0 - - /// Swift equivalent: `UInt64` - var uIntValue64: UInt64 = 0 - - /// Swift equivalent: `SignedValue` - var sIntValue32: Int32 = 0 - - /// Swift equivalent: `SignedValue` - var sIntValue64: Int64 = 0 - - /// Swift equivalent: `FixedSize` - var fIntValue32: UInt32 = 0 - - /// Swift equivalent: `FixedSize` - var fIntValue64: UInt64 = 0 - - /// Swift equivalent: `FixedSize` - var sfIntValue32: Int32 = 0 - - /// Swift equivalent: `FixedSize` - var sfIntValue64: Int64 = 0 - - /// Swift equivalent: `Bool` - var boolValue: Bool = false - - /// Swift equivalent: `String` - var stringValue: String = String() - - /// Swift equivalent: `Data` - var dataValue: Data = Data() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct FieldNumberTest { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var low: Bool = false - - var high: Bool = false - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct EnumContainer { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var selection: EnumContainer.Selection = .default - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum Selection: SwiftProtobuf.Enum { - typealias RawValue = Int - case `default` // = 0 - case one // = 1 - case UNRECOGNIZED(Int) - - init() { - self = .default - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .default - case 1: self = .one - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .default: return 0 - case .one: return 1 - case .UNRECOGNIZED(let i): return i - } - } - - } - - init() {} -} - -#if swift(>=4.2) - -extension EnumContainer.Selection: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [EnumContainer.Selection] = [ - .default, - .one, - ] -} - -#endif // swift(>=4.2) - -struct OneOfContainer { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var alternatives: OneOfContainer.OneOf_Alternatives? = nil - - var integer: Int64 { - get { - if case .integer(let v)? = alternatives {return v} - return 0 - } - set {alternatives = .integer(newValue)} - } - - var text: String { - get { - if case .text(let v)? = alternatives {return v} - return String() - } - set {alternatives = .text(newValue)} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum OneOf_Alternatives: Equatable { - case integer(Int64) - case text(String) - - #if !swift(>=4.1) - static func ==(lhs: OneOfContainer.OneOf_Alternatives, rhs: OneOfContainer.OneOf_Alternatives) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.integer, .integer): return { - guard case .integer(let l) = lhs, case .integer(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.text, .text): return { - guard case .text(let l) = lhs, case .text(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif - } - - init() {} -} - -#if swift(>=5.5) && canImport(_Concurrency) -extension SimpleStruct: @unchecked Sendable {} -extension WrappedContainer: @unchecked Sendable {} -extension Outer: @unchecked Sendable {} -extension Outer2: @unchecked Sendable {} -extension MapContainer: @unchecked Sendable {} -extension PrimitiveTypesContainer: @unchecked Sendable {} -extension FieldNumberTest: @unchecked Sendable {} -extension EnumContainer: @unchecked Sendable {} -extension EnumContainer.Selection: @unchecked Sendable {} -extension OneOfContainer: @unchecked Sendable {} -extension OneOfContainer.OneOf_Alternatives: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -extension SimpleStruct: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "SimpleStruct" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "integer64"), - 2: .same(proto: "text"), - 3: .same(proto: "data"), - 4: .same(proto: "intArray"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt64Field(value: &self.integer64) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.text) }() - case 3: try { try decoder.decodeSingularBytesField(value: &self.data) }() - case 4: try { try decoder.decodeRepeatedUInt32Field(value: &self.intArray) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.integer64 != 0 { - try visitor.visitSingularInt64Field(value: self.integer64, fieldNumber: 1) - } - if !self.text.isEmpty { - try visitor.visitSingularStringField(value: self.text, fieldNumber: 2) - } - if !self.data.isEmpty { - try visitor.visitSingularBytesField(value: self.data, fieldNumber: 3) - } - if !self.intArray.isEmpty { - try visitor.visitPackedUInt32Field(value: self.intArray, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SimpleStruct, rhs: SimpleStruct) -> Bool { - if lhs.integer64 != rhs.integer64 {return false} - if lhs.text != rhs.text {return false} - if lhs.data != rhs.data {return false} - if lhs.intArray != rhs.intArray {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension WrappedContainer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "WrappedContainer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "fourByteInt"), - 2: .same(proto: "fourByteUInt"), - 3: .same(proto: "eightByteInt"), - 4: .same(proto: "eightByteUInt"), - 5: .same(proto: "signed32"), - 6: .same(proto: "signed64"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularSFixed32Field(value: &self.fourByteInt) }() - case 2: try { try decoder.decodeSingularFixed32Field(value: &self.fourByteUint) }() - case 3: try { try decoder.decodeSingularSFixed64Field(value: &self.eightByteInt) }() - case 4: try { try decoder.decodeSingularFixed64Field(value: &self.eightByteUint) }() - case 5: try { try decoder.decodeSingularSInt32Field(value: &self.signed32) }() - case 6: try { try decoder.decodeSingularSInt64Field(value: &self.signed64) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.fourByteInt != 0 { - try visitor.visitSingularSFixed32Field(value: self.fourByteInt, fieldNumber: 1) - } - if self.fourByteUint != 0 { - try visitor.visitSingularFixed32Field(value: self.fourByteUint, fieldNumber: 2) - } - if self.eightByteInt != 0 { - try visitor.visitSingularSFixed64Field(value: self.eightByteInt, fieldNumber: 3) - } - if self.eightByteUint != 0 { - try visitor.visitSingularFixed64Field(value: self.eightByteUint, fieldNumber: 4) - } - if self.signed32 != 0 { - try visitor.visitSingularSInt32Field(value: self.signed32, fieldNumber: 5) - } - if self.signed64 != 0 { - try visitor.visitSingularSInt64Field(value: self.signed64, fieldNumber: 6) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: WrappedContainer, rhs: WrappedContainer) -> Bool { - if lhs.fourByteInt != rhs.fourByteInt {return false} - if lhs.fourByteUint != rhs.fourByteUint {return false} - if lhs.eightByteInt != rhs.eightByteInt {return false} - if lhs.eightByteUint != rhs.eightByteUint {return false} - if lhs.signed32 != rhs.signed32 {return false} - if lhs.signed64 != rhs.signed64 {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Outer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "Outer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "inner"), - 2: .same(proto: "more"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._inner) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._more) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._inner { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._more { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Outer, rhs: Outer) -> Bool { - if lhs._inner != rhs._inner {return false} - if lhs._more != rhs._more {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Outer2: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "Outer2" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "values"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeRepeatedMessageField(value: &self.values) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.values.isEmpty { - try visitor.visitRepeatedMessageField(value: self.values, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Outer2, rhs: Outer2) -> Bool { - if lhs.values != rhs.values {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension MapContainer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "MapContainer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "x"), - 2: .same(proto: "y"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.x) }() - case 2: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.y) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.x.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.x, fieldNumber: 1) - } - if !self.y.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.y, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: MapContainer, rhs: MapContainer) -> Bool { - if lhs.x != rhs.x {return false} - if lhs.y != rhs.y {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension PrimitiveTypesContainer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "PrimitiveTypesContainer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "doubleValue"), - 2: .same(proto: "floatValue"), - 3: .same(proto: "intValue32"), - 4: .same(proto: "intValue64"), - 5: .same(proto: "uIntValue32"), - 6: .same(proto: "uIntValue64"), - 7: .same(proto: "sIntValue32"), - 8: .same(proto: "sIntValue64"), - 9: .same(proto: "fIntValue32"), - 10: .same(proto: "fIntValue64"), - 11: .same(proto: "sfIntValue32"), - 12: .same(proto: "sfIntValue64"), - 13: .same(proto: "boolValue"), - 14: .same(proto: "stringValue"), - 15: .same(proto: "dataValue"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularDoubleField(value: &self.doubleValue) }() - case 2: try { try decoder.decodeSingularFloatField(value: &self.floatValue) }() - case 3: try { try decoder.decodeSingularInt32Field(value: &self.intValue32) }() - case 4: try { try decoder.decodeSingularInt64Field(value: &self.intValue64) }() - case 5: try { try decoder.decodeSingularUInt32Field(value: &self.uIntValue32) }() - case 6: try { try decoder.decodeSingularUInt64Field(value: &self.uIntValue64) }() - case 7: try { try decoder.decodeSingularSInt32Field(value: &self.sIntValue32) }() - case 8: try { try decoder.decodeSingularSInt64Field(value: &self.sIntValue64) }() - case 9: try { try decoder.decodeSingularFixed32Field(value: &self.fIntValue32) }() - case 10: try { try decoder.decodeSingularFixed64Field(value: &self.fIntValue64) }() - case 11: try { try decoder.decodeSingularSFixed32Field(value: &self.sfIntValue32) }() - case 12: try { try decoder.decodeSingularSFixed64Field(value: &self.sfIntValue64) }() - case 13: try { try decoder.decodeSingularBoolField(value: &self.boolValue) }() - case 14: try { try decoder.decodeSingularStringField(value: &self.stringValue) }() - case 15: try { try decoder.decodeSingularBytesField(value: &self.dataValue) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.doubleValue != 0 { - try visitor.visitSingularDoubleField(value: self.doubleValue, fieldNumber: 1) - } - if self.floatValue != 0 { - try visitor.visitSingularFloatField(value: self.floatValue, fieldNumber: 2) - } - if self.intValue32 != 0 { - try visitor.visitSingularInt32Field(value: self.intValue32, fieldNumber: 3) - } - if self.intValue64 != 0 { - try visitor.visitSingularInt64Field(value: self.intValue64, fieldNumber: 4) - } - if self.uIntValue32 != 0 { - try visitor.visitSingularUInt32Field(value: self.uIntValue32, fieldNumber: 5) - } - if self.uIntValue64 != 0 { - try visitor.visitSingularUInt64Field(value: self.uIntValue64, fieldNumber: 6) - } - if self.sIntValue32 != 0 { - try visitor.visitSingularSInt32Field(value: self.sIntValue32, fieldNumber: 7) - } - if self.sIntValue64 != 0 { - try visitor.visitSingularSInt64Field(value: self.sIntValue64, fieldNumber: 8) - } - if self.fIntValue32 != 0 { - try visitor.visitSingularFixed32Field(value: self.fIntValue32, fieldNumber: 9) - } - if self.fIntValue64 != 0 { - try visitor.visitSingularFixed64Field(value: self.fIntValue64, fieldNumber: 10) - } - if self.sfIntValue32 != 0 { - try visitor.visitSingularSFixed32Field(value: self.sfIntValue32, fieldNumber: 11) - } - if self.sfIntValue64 != 0 { - try visitor.visitSingularSFixed64Field(value: self.sfIntValue64, fieldNumber: 12) - } - if self.boolValue != false { - try visitor.visitSingularBoolField(value: self.boolValue, fieldNumber: 13) - } - if !self.stringValue.isEmpty { - try visitor.visitSingularStringField(value: self.stringValue, fieldNumber: 14) - } - if !self.dataValue.isEmpty { - try visitor.visitSingularBytesField(value: self.dataValue, fieldNumber: 15) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: PrimitiveTypesContainer, rhs: PrimitiveTypesContainer) -> Bool { - if lhs.doubleValue != rhs.doubleValue {return false} - if lhs.floatValue != rhs.floatValue {return false} - if lhs.intValue32 != rhs.intValue32 {return false} - if lhs.intValue64 != rhs.intValue64 {return false} - if lhs.uIntValue32 != rhs.uIntValue32 {return false} - if lhs.uIntValue64 != rhs.uIntValue64 {return false} - if lhs.sIntValue32 != rhs.sIntValue32 {return false} - if lhs.sIntValue64 != rhs.sIntValue64 {return false} - if lhs.fIntValue32 != rhs.fIntValue32 {return false} - if lhs.fIntValue64 != rhs.fIntValue64 {return false} - if lhs.sfIntValue32 != rhs.sfIntValue32 {return false} - if lhs.sfIntValue64 != rhs.sfIntValue64 {return false} - if lhs.boolValue != rhs.boolValue {return false} - if lhs.stringValue != rhs.stringValue {return false} - if lhs.dataValue != rhs.dataValue {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension FieldNumberTest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "FieldNumberTest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "low"), - 536870911: .same(proto: "high"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularBoolField(value: &self.low) }() - case 536870911: try { try decoder.decodeSingularBoolField(value: &self.high) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.low != false { - try visitor.visitSingularBoolField(value: self.low, fieldNumber: 1) - } - if self.high != false { - try visitor.visitSingularBoolField(value: self.high, fieldNumber: 536870911) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: FieldNumberTest, rhs: FieldNumberTest) -> Bool { - if lhs.low != rhs.low {return false} - if lhs.high != rhs.high {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension EnumContainer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "EnumContainer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "selection"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.selection) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.selection != .default { - try visitor.visitSingularEnumField(value: self.selection, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: EnumContainer, rhs: EnumContainer) -> Bool { - if lhs.selection != rhs.selection {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension EnumContainer.Selection: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "DEFAULT"), - 1: .same(proto: "ONE"), - ] -} - -extension OneOfContainer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "OneOfContainer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "integer"), - 2: .same(proto: "text"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { - var v: Int64? - try decoder.decodeSingularInt64Field(value: &v) - if let v = v { - if self.alternatives != nil {try decoder.handleConflictingOneOf()} - self.alternatives = .integer(v) - } - }() - case 2: try { - var v: String? - try decoder.decodeSingularStringField(value: &v) - if let v = v { - if self.alternatives != nil {try decoder.handleConflictingOneOf()} - self.alternatives = .text(v) - } - }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - switch self.alternatives { - case .integer?: try { - guard case .integer(let v)? = self.alternatives else { preconditionFailure() } - try visitor.visitSingularInt64Field(value: v, fieldNumber: 1) - }() - case .text?: try { - guard case .text(let v)? = self.alternatives else { preconditionFailure() } - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - }() - case nil: break - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: OneOfContainer, rhs: OneOfContainer) -> Bool { - if lhs.alternatives != rhs.alternatives {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Tests/BinaryCodableTests/Proto/TestTypes.proto b/Tests/BinaryCodableTests/Proto/TestTypes.proto deleted file mode 100644 index 5379ce7..0000000 --- a/Tests/BinaryCodableTests/Proto/TestTypes.proto +++ /dev/null @@ -1,129 +0,0 @@ -syntax = "proto3"; - -message SimpleStruct { - - // Equivalent to Swift `Int64` - int64 integer64 = 1; - - // Equivalent to Swift `String` - string text = 2; - - // Equivalent to Swift `Data` - bytes data = 3; - - // Equivalent to Swift `[UInt32]` - repeated uint32 intArray = 4; -} - -message WrappedContainer { - - // Equivalent to Swift `FixedSize` - sfixed32 fourByteInt = 1; - - // Equivalent to Swift `FixedSize` - fixed32 fourByteUInt = 2; - - // Equivalent to Swift `FixedSize` - sfixed64 eightByteInt = 3; - - // Equivalent to Swift `FixedSize` - fixed64 eightByteUInt = 4; - - // Equivalent to Swift `SignedValue` - sint32 signed32 = 5; - - // Equivalent to Swift `SignedValue` - sint64 signed64 = 6; -} - -message Outer { - - SimpleStruct inner = 1; - - SimpleStruct more = 2; -} - -message Outer2 { - - repeated SimpleStruct values = 1; -} - -// A container to test different map types -message MapContainer { - - map x = 1; - - map y = 2; -} - -message PrimitiveTypesContainer { - - // Swift equivalent: `Double` - double doubleValue = 1; - - // Swift equivalent: `Float` - float floatValue = 2; - - // Swift equivalent: `Int32` - int32 intValue32 = 3; - - // Swift equivalent: `Int64` - int64 intValue64 = 4; - - // Swift equivalent: `UInt32` - uint32 uIntValue32 = 5; - - // Swift equivalent: `UInt64` - uint64 uIntValue64 = 6; - - // Swift equivalent: `SignedValue` - sint32 sIntValue32 = 7; - - // Swift equivalent: `SignedValue` - sint64 sIntValue64 = 8; - - // Swift equivalent: `FixedSize` - fixed32 fIntValue32 = 9; - - // Swift equivalent: `FixedSize` - fixed64 fIntValue64 = 10; - - // Swift equivalent: `FixedSize` - sfixed32 sfIntValue32 = 11; - - // Swift equivalent: `FixedSize` - sfixed64 sfIntValue64 = 12; - - // Swift equivalent: `Bool` - bool boolValue = 13; - - // Swift equivalent: `String` - string stringValue = 14; - - // Swift equivalent: `Data` - bytes dataValue = 15; -} - -message FieldNumberTest { - - bool low = 1; - - bool high = 0x1FFFFFFF; -} - -message EnumContainer { - - enum Selection { - DEFAULT = 0; - ONE = 1; - } - Selection selection = 1; -} - -message OneOfContainer { - - oneof alternatives { - int64 integer = 1; - string text = 2; - } -} diff --git a/Tests/BinaryCodableTests/ProtobufCompatibilityTests.swift b/Tests/BinaryCodableTests/ProtobufCompatibilityTests.swift deleted file mode 100644 index 001ea1a..0000000 --- a/Tests/BinaryCodableTests/ProtobufCompatibilityTests.swift +++ /dev/null @@ -1,407 +0,0 @@ -import XCTest -import BinaryCodable -import SwiftProtobuf - -private struct Simple: Codable, Equatable { - - var integer64: Int64 - - var text: String - - var data: Data - - var intArray: [UInt32] - - enum CodingKeys: Int, CodingKey { - case integer64 = 1 - case text = 2 - case data = 3 - case intArray = 4 - } -} - -extension Simple { - - var proto: SimpleStruct { - .with { - $0.integer64 = integer64 - $0.text = text - $0.data = data - $0.intArray = intArray - } - } -} - -private struct MapTest: Codable, Equatable { - let x: [String: Data] - let y: [UInt32: String] - - enum CodingKeys: Int, CodingKey { - case x = 1 - case y = 2 - } -} - -final class ProtobufCompatibilityTests: XCTestCase { - - func testProtoToCodable(_ value: SwiftProtobuf.Message, expected: T) throws where T: Decodable, T: Equatable { - let data = try value.serializedData() - - let decoder = ProtobufDecoder() - - do { - let decoded = try decoder.decode(T.self, from: data) - XCTAssertEqual(decoded, expected) - } catch { - print(Array(data)) - throw error - } - } - - func testCodableToProto(_ value: T, expected: P) throws where T: Encodable, P: SwiftProtobuf.Message, P: Equatable { - let encoder = ProtobufEncoder() - - let data = try encoder.encode(value) - do { - let decoded = try P.init(serializedData: data) - XCTAssertEqual(decoded, expected) - } catch { - print(Array(data)) - throw error - } - } - - private let simple = Simple( - integer64: 123, - text: "Some", - data: Data(repeating: 42, count: 2), - intArray: [0, .max, 2]) - - func testCompatibilityStruct() throws { - - try testProtoToCodable(simple.proto, expected: simple) - try testCodableToProto(simple, expected: simple.proto) - } - - func testCompatibilityWrappers() throws { - struct Test: Codable, Equatable { - - @FixedSize - var fixed32: Int32 - - @FixedSize - var fixedU32: UInt32 - - @FixedSize - var fixed64: Int64 - - @FixedSize - var fixedU64: UInt64 - - @SignedValue - var signed32: Int32 - - @SignedValue - var signed64: Int64 - - enum CodingKeys: Int, CodingKey { - case fixed32 = 1 - case fixedU32 = 2 - case fixed64 = 3 - case fixedU64 = 4 - case signed32 = 5 - case signed64 = 6 - } - } - let fixed32: Int32 = -123 - let fixedU32: UInt32 = 123 - let fixed64: Int64 = -123456789012 - let fixedU64: UInt64 = 123456789012 - let signed32: Int32 = 1234 - let signed64: Int64 = 123456789 - - let codableValue = Test( - fixed32: fixed32, - fixedU32: fixedU32, - fixed64: fixed64, - fixedU64: fixedU64, - signed32: signed32, - signed64: signed64) - - let protoValue = WrappedContainer.with { - $0.fourByteInt = fixed32 - $0.fourByteUint = fixedU32 - $0.eightByteInt = fixed64 - $0.eightByteUint = fixedU64 - $0.signed32 = signed32 - $0.signed64 = signed64 - } - - try testProtoToCodable(protoValue, expected: codableValue) - try testCodableToProto(codableValue, expected: protoValue) - - let emptyCodable = Test( - fixed32: 0, - fixedU32: 0, - fixed64: 0, - fixedU64: 0, - signed32: 0, - signed64: 0) - - let emptyProto = WrappedContainer() - - XCTAssertEqual(try emptyProto.serializedData(), Data()) - XCTAssertEqual(try ProtobufEncoder().encode(emptyCodable), Data()) - - try testProtoToCodable(protoValue, expected: codableValue) - try testCodableToProto(codableValue, expected: protoValue) - - } - - func testNormalIntegerEncoding() throws { - struct Test: Codable, Equatable { - - var integer: Int32 - - enum CodingKeys: Int, CodingKey { - case integer = 1 - } - } - - let codable = Test(integer: 123) - let data = try ProtobufEncoder().encode(codable) - XCTAssertEqual(Array(data), [8, 123]) - } - - func testNestedStructs() throws { - struct Wrapped: Codable, Equatable { - - let inner: Simple - - let more: Simple - - enum CodingKeys: Int, CodingKey { - case inner = 1 - case more = 2 - } - } - - let more = Simple( - integer64: 123, - text: "More", - data: .init(repeating: 56, count: 5), - intArray: [0, 255, .max]) - - let value = Wrapped( - inner: simple, - more: more) - - let proto = Outer.with { - $0.inner = simple.proto - $0.more = more.proto - } - - try testProtoToCodable(proto, expected: value) - try testCodableToProto(value, expected: proto) - } - - func testStructArrays() throws { - struct Wrapped: Codable, Equatable { - - let values: [Simple] - - enum CodingKeys: Int, CodingKey { - case values = 1 - } - } - - let more = Simple(integer64: 123, text: "More", data: .init(repeating: 56, count: 5), intArray: [0, 255, .max]) - - let value = Wrapped(values: [simple, more]) - - let proto = Outer2.with { - $0.values = [simple.proto, more.proto] - } - - try testCodableToProto(value, expected: proto) - try testProtoToCodable(proto, expected: value) - } - - func testProtoMaps() throws { - let x: [String: Data] = ["a" : .init(repeating: 2, count: 2), "b": .init(repeating: 1, count: 1)] - let y: [UInt32: String] = [123: "a", 234: "b"] - - let proto = MapContainer.with { - $0.x = x - $0.y = y - } - let codable = MapTest(x: x, y: y) - - try testCodableToProto(codable, expected: proto) - try testProtoToCodable(proto, expected: codable) - } - - func testProtoMapsWithDefaultValues() throws { - let x: [String: Data] = ["" : .init(repeating: 2, count: 2), "b": Data()] - let y: [UInt32: String] = [0: "a", 234: ""] - - let proto = MapContainer.with { - $0.x = x - $0.y = y - } - let codable = MapTest(x: x, y: y) - - try testCodableToProto(codable, expected: proto) - try testProtoToCodable(proto, expected: codable) - } - - func testDefaultValues() throws { - let codable = Simple( - integer64: 0, - text: "", - data: Data(), - intArray: []) - - let proto = codable.proto - - try testCodableToProto(codable, expected: proto) - try testProtoToCodable(proto, expected: codable) - } - - func testFieldNumberBounds() throws { - struct FieldBounds: Codable, Equatable { - let low: Bool - let high: Bool - - enum CodingKeys: Int, CodingKey { - case low = 1 - case high = 536870911 - } - } - - let codable = FieldBounds(low: true, high: true) - let proto = FieldNumberTest.with { - $0.low = true - $0.high = true - } - try testCodableToProto(codable, expected: proto) - try testProtoToCodable(proto, expected: codable) - - struct FieldOutOfLowBounds: Codable, Equatable { - let low: Bool - let high: Bool - - enum CodingKeys: Int, CodingKey { - case low = 0 - case high = 536870911 - } - } - let codable2 = FieldOutOfLowBounds(low: true, high: true) - do { - let encoder = ProtobufEncoder() - _ = try encoder.encode(codable2) - } catch ProtobufEncodingError.integerKeyOutOfRange { - - } - - struct FieldOutOfHighBounds: Codable, Equatable { - let low: Bool - let high: Bool - - enum CodingKeys: Int, CodingKey { - case low = 1 - case high = 536870912 - } - } - let codable3 = FieldOutOfHighBounds(low: true, high: true) - do { - let encoder = ProtobufEncoder() - _ = try encoder.encode(codable3) - } catch ProtobufEncodingError.integerKeyOutOfRange { - - } - } - - func testEnum() throws { - struct EnumTest: Codable, Equatable { - let selection: Selection - - enum CodingKeys: Int, CodingKey { - case selection = 1 - } - - enum Selection: Int, Codable { - case `default` = 0 - case one = 1 - } - } - - let codable = EnumTest(selection: .one) - - let proto = EnumContainer.with { $0.selection = .one } - - try testCodableToProto(codable, expected: proto) - try testProtoToCodable(proto, expected: codable) - - let codable2 = EnumTest(selection: .default) - - let proto2 = EnumContainer.with { $0.selection = .default } - - try testCodableToProto(codable2, expected: proto2) - try testProtoToCodable(proto2, expected: codable2) - } - - func testOneOf() throws { - struct Test: Codable, Equatable { - - enum OneOf: Codable, Equatable, ProtobufOneOf { - case integer(Int64) - case string(String) - - enum CodingKeys: Int, CodingKey { - case integer = 1 - case string = 2 - } - } - - let alternatives: OneOf - - enum CodingKeys: Int, CodingKey { - case alternatives = 12345 // Doesn't matter, not used - } - } - - let codable1 = Test(alternatives: .integer(123)) - let proto1 = OneOfContainer.with { - $0.integer = 123 - } - - try testCodableToProto(codable1, expected: proto1) - try testProtoToCodable(proto1, expected: codable1) - - let codable2 = Test(alternatives: .string("Some")) - let proto2 = OneOfContainer.with { - $0.text = "Some" - } - - try testCodableToProto(codable2, expected: proto2) - try testProtoToCodable(proto2, expected: codable2) - - let codable3 = Test(alternatives: .string("")) - let proto3 = OneOfContainer.with { - $0.text = "" - } - - try testProtoToCodable(proto3, expected: codable3) - try testCodableToProto(codable3, expected: proto3) - - let codable4 = Test(alternatives: .integer(0)) - let proto4 = OneOfContainer.with { - $0.integer = 0 - } - - try testCodableToProto(codable4, expected: proto4) - try testProtoToCodable(proto4, expected: codable4) - - } -} diff --git a/Tests/BinaryCodableTests/ProtobufDescriptionTests.swift b/Tests/BinaryCodableTests/ProtobufDescriptionTests.swift deleted file mode 100644 index aeb741d..0000000 --- a/Tests/BinaryCodableTests/ProtobufDescriptionTests.swift +++ /dev/null @@ -1,63 +0,0 @@ -import XCTest -@testable import BinaryCodable - -final class ProtobufDescriptionTests: XCTestCase { - - private func failingProtobufDefinition(_ value: T) where T: Encodable { - do { - let definition = try ProtobufEncoder().getProtobufDefinition(value) - XCTFail("Created invalid definition for \(type(of: value)): \(definition)") - } catch is ProtobufEncodingError { - - } catch { - XCTFail("Failed protobuf definition for \(type(of: value)) with error: \(error)") - } - } - - func testFailPrimitiveTypes() throws { - failingProtobufDefinition(123) - failingProtobufDefinition(Int32(123)) - } - - func testStructDefinition() throws { - struct Test: Codable { - - var integer64: Int64 - - var text: String - - var data: Data - - var intArray: [UInt32] - - enum CodingKeys: Int, CodingKey { - case integer64 = 1 - case text = 2 - case data = 3 - case intArray = 4 - } - } - let value = Test( - integer64: 123, - text: "", - data: Data([1]), - intArray: [1, 2]) - let definition = try ProtobufEncoder().getProtobufDefinition(value) - let expected = - """ - syntax = "proto3"; - - message Test { - - sint64 integer64 = 1; - - string text = 2; - - bytes data = 3; - - repeated uint32 intArray = 4; - } - """ - XCTAssertEqual(definition, expected) - } -} diff --git a/Tests/BinaryCodableTests/ProtobufPrimitiveTests.swift b/Tests/BinaryCodableTests/ProtobufPrimitiveTests.swift deleted file mode 100644 index 07041b1..0000000 --- a/Tests/BinaryCodableTests/ProtobufPrimitiveTests.swift +++ /dev/null @@ -1,164 +0,0 @@ -import XCTest -@testable import BinaryCodable - -private struct CodablePrimitiveContainer: Codable, Equatable { - - var doubleValue: Double = .zero - var floatValue: Float = .zero - var intValue32: Int32 = .zero - var intValue64: Int64 = .zero - var uIntValue32: UInt32 = .zero - var uIntValue64: UInt64 = .zero - @SignedValue var sIntValue32: Int32 = .zero - @SignedValue var sIntValue64: Int64 = .zero - @FixedSize var fIntValue32: UInt32 = .zero - @FixedSize var fIntValue64: UInt64 = .zero - @FixedSize var sfIntValue32: Int32 = .zero - @FixedSize var sfIntValue64: Int64 = .zero - var boolValue: Bool = .zero - var stringValue: String = .zero - var dataValue: Data = .zero - - enum CodingKeys: Int, CodingKey { - case doubleValue = 1 - case floatValue = 2 - case intValue32 = 3 - case intValue64 = 4 - case uIntValue32 = 5 - case uIntValue64 = 6 - case sIntValue32 = 7 - case sIntValue64 = 8 - case fIntValue32 = 9 - case fIntValue64 = 10 - case sfIntValue32 = 11 - case sfIntValue64 = 12 - case boolValue = 13 - case stringValue = 14 - case dataValue = 15 - } - - var proto: PrimitiveTypesContainer { - .with { - $0.doubleValue = doubleValue - $0.floatValue = floatValue - $0.intValue32 = intValue32 - $0.intValue64 = intValue64 - $0.uIntValue32 = uIntValue32 - $0.uIntValue64 = uIntValue64 - $0.sIntValue32 = sIntValue32 - $0.sIntValue64 = sIntValue64 - $0.fIntValue32 = fIntValue32 - $0.fIntValue64 = fIntValue64 - $0.sfIntValue32 = sfIntValue32 - $0.sfIntValue64 = sfIntValue64 - $0.boolValue = boolValue - $0.stringValue = stringValue - $0.dataValue = dataValue - } - } -} - -final class ProtobufPrimitiveTests: XCTestCase { - - private func compare(_ value: CodablePrimitiveContainer, to expected: [UInt8]) throws { - let protoData = try value.proto.serializedData() - XCTAssertEqual(Array(protoData), expected) - let encoder = ProtobufEncoder() - let codableData = try encoder.encode(value) - XCTAssertEqual(Array(codableData), expected) - - let decoder = ProtobufDecoder() - let decoded = try decoder.decode(CodablePrimitiveContainer.self, from: codableData) - XCTAssertEqual(value, decoded) - } - - func testDoubleValue() throws { - let value = CodablePrimitiveContainer(doubleValue: 123) - let expected: [UInt8] = [ 1 << 3 | 1, 0, 0, 0, 0, 0, 192, 94, 64] - try compare(value, to: expected) - } - - func testFloatValue() throws { - let value = CodablePrimitiveContainer(floatValue: 123) - let expected: [UInt8] = [ 2 << 3 | 5, 0, 0, 246, 66] - try compare(value, to: expected) - } - - func testInt32Value() throws { - let value = CodablePrimitiveContainer(intValue32: 123) - let expected: [UInt8] = [ 3 << 3 | 0, 123] - try compare(value, to: expected) - } - - func testInt64Value() throws { - let value = CodablePrimitiveContainer(intValue64: 123) - let expected: [UInt8] = [ 4 << 3 | 0, 123] - try compare(value, to: expected) - } - - func testUInt32Value() throws { - let value = CodablePrimitiveContainer(uIntValue32: 123) - let expected: [UInt8] = [ 5 << 3 | 0, 123] - try compare(value, to: expected) - } - - func testUInt64Value() throws { - let value = CodablePrimitiveContainer(uIntValue64: 123) - let expected: [UInt8] = [ 6 << 3 | 0, 123] - try compare(value, to: expected) - } - - func testSInt32Value() throws { - let value = CodablePrimitiveContainer(sIntValue32: 123) - let expected: [UInt8] = [ 7 << 3 | 0, 246, 1] - try compare(value, to: expected) - } - - func testSInt64Value() throws { - let value = CodablePrimitiveContainer(sIntValue64: 123) - let expected: [UInt8] = [ 8 << 3 | 0, 246, 1] - try compare(value, to: expected) - } - - func testFInt32Value() throws { - let value = CodablePrimitiveContainer(fIntValue32: 123) - let expected: [UInt8] = [ 9 << 3 | 5, 123, 0, 0, 0] - try compare(value, to: expected) - } - - func testFInt64Value() throws { - let value = CodablePrimitiveContainer(fIntValue64: 123) - let expected: [UInt8] = [ 10 << 3 | 1, 123, 0, 0, 0, 0, 0, 0, 0] - try compare(value, to: expected) - } - - func testFUInt32Value() throws { - let value = CodablePrimitiveContainer(sfIntValue32: 123) - let expected: [UInt8] = [ 11 << 3 | 5, 123, 0, 0, 0] - try compare(value, to: expected) - } - - func testFUInt64Value() throws { - let value = CodablePrimitiveContainer(sfIntValue64: 123) - let expected: [UInt8] = [ 12 << 3 | 1, 123, 0, 0, 0, 0, 0, 0, 0] - try compare(value, to: expected) - } - - func testBoolValue() throws { - let value = CodablePrimitiveContainer(boolValue: true) - let expected: [UInt8] = [ 13 << 3 | 0, 1] - try compare(value, to: expected) - } - - func testStringValue() throws { - let value = CodablePrimitiveContainer(stringValue: "abc") - let expected: [UInt8] = [ 14 << 3 | 2, 3, 97, 98, 99] - try compare(value, to: expected) - } - - func testDataValue() throws { - let value = CodablePrimitiveContainer(dataValue: .init(repeating: 2, count: 3)) - let expected: [UInt8] = [ 15 << 3 | 2, 3, 2, 2, 2] - try compare(value, to: expected) - } -} diff --git a/Tests/BinaryCodableTests/SIMDTests.swift b/Tests/BinaryCodableTests/SIMDTests.swift new file mode 100644 index 0000000..12dba73 --- /dev/null +++ b/Tests/BinaryCodableTests/SIMDTests.swift @@ -0,0 +1,18 @@ +import Foundation +import XCTest +@testable import BinaryCodable +#if canImport(simd) +import simd + + +final class SIMDTests: XCTestCase { + + func testSIMDDouble() throws { + let double = 3.14 + let value = SIMD2(x: double, y: double) + // Double has length 8, so prepend 16 + let doubleData = [16] + Array(double.encodedData) + try compare(value, to: doubleData + doubleData) + } +} +#endif diff --git a/Tests/BinaryCodableTests/SequenceEncoderTests.swift b/Tests/BinaryCodableTests/SequenceEncoderTests.swift index b3869f4..f43e62f 100644 --- a/Tests/BinaryCodableTests/SequenceEncoderTests.swift +++ b/Tests/BinaryCodableTests/SequenceEncoderTests.swift @@ -7,17 +7,16 @@ final class SequenceEncoderTests: XCTestCase { let encoder = BinaryStreamEncoder() let bytes = try input.map(encoder.encode).joinedData - + print(Array(bytes)) let decoder = BinaryStreamDecoder() decoder.add(bytes) let decoded = try decoder.decodeElements() - print(Array(bytes)) XCTAssertEqual(decoded, input) } func testIntegerEncoding() throws { - try encodeSequence([1,2,3]) + try encodeSequence([1, 2, 3]) try encodeSequence([1.0, 2.0, 3.0]) try encodeSequence([true, false, true]) try encodeSequence(["Some", "Text", "More"]) @@ -65,7 +64,7 @@ final class SequenceEncoderTests: XCTestCase { } let input = [Test(a: 1, b: "Some"), Test(a: 2, b: "Text"), Test(a: 3, b: "More")] - let enc = BinaryEncoder() + var enc = BinaryEncoder() enc.sortKeysDuringEncoding = true let encoder = BinaryStreamEncoder(encoder: enc) var data = try encoder.encode(contentsOf: input) @@ -90,7 +89,7 @@ final class SequenceEncoderTests: XCTestCase { } let input = [Test(a: 1, b: "Some"), Test(a: 2, b: "Text"), Test(a: 3, b: "More")] - let enc = BinaryEncoder() + var enc = BinaryEncoder() enc.sortKeysDuringEncoding = true let encoder = BinaryStreamEncoder(encoder: enc) var data = try encoder.encode(contentsOf: input) @@ -104,4 +103,3 @@ final class SequenceEncoderTests: XCTestCase { XCTAssertEqual(decoded.elements, [input[0], input[1]]) } } - diff --git a/Tests/BinaryCodableTests/StructEncodingTests.swift b/Tests/BinaryCodableTests/StructEncodingTests.swift index e1c9a49..f06dc84 100644 --- a/Tests/BinaryCodableTests/StructEncodingTests.swift +++ b/Tests/BinaryCodableTests/StructEncodingTests.swift @@ -7,8 +7,13 @@ final class StructEncodingTests: XCTestCase { struct Test: Codable, Equatable { let val: [Bool] } - let expected: [UInt8] = [0b00111010, 118, 97, 108, - 3, 1, 0, 1] + let expected: [UInt8] = [ + 7, // String key, length 3 + 118, 97, 108, + 12, // Length 6 + 2, 1, // true + 2, 0, // false + 2, 1] // true try compare(Test(val: [true, false, true]), to: expected) } @@ -18,11 +23,15 @@ final class StructEncodingTests: XCTestCase { } let value = [Test(val: 123), Test(val: 124)] let expected: [UInt8] = [ - 6, // Length of first element - 0b00111000, 118, 97, 108, // String key 'val', varint + 14, // Length 7 + 7, // String key, length 3 + 118, 97, 108, // 'val' + 4, // Length 2 246, 1, // Value '123' - 6, // Length of second element - 0b00111000, 118, 97, 108, // String key 'val', varint + 14, // Length 7 + 7, // String key, length 3 + 118, 97, 108, // 'val' + 4, // Length 2 248, 1, // Value '124' ] try compare(value, to: expected) @@ -34,14 +43,16 @@ final class StructEncodingTests: XCTestCase { } let value: [Test?] = [Test(val: 123), nil, Test(val: 124)] let expected: [UInt8] = [ - 1, // First element not nil - 6, // Length of first element - 0b00111000, 118, 97, 108, // String key 'val', varint + 14, // Not nil, length 7 + 7, // String key, length 3 + 118, 97, 108, // 'val' + 4, // Length 2 246, 1, // Value '123' - 0, // Second element is nil - 1, // Third element not nil - 6, // Length of third element - 0b00111000, 118, 97, 108, // String key 'val', varint + 1, // Nil + 14, // Not nil, length 7 + 7, // String key, length 3 + 118, 97, 108, // 'val' + 4, // Length 2 248, 1, // Value '124' ] try compare(value, to: expected) @@ -55,101 +66,109 @@ final class StructEncodingTests: XCTestCase { case val = -1 } } - let value = Test(val: true) - let expected: [UInt8] = [ - 0b11110000, // Int key, varint, three LSB of int key - 255, 255, 255, 255, 255, 255, 255, 255, - 1, /// Bool `true` - ] - try compare(value, to: expected) + let encoder = BinaryEncoder() + do { + _ = try encoder.encode(Test(val: true)) + } catch let error as EncodingError { + guard case .invalidValue(let any, let context) = error else { + XCTFail() + return + } + XCTAssertEqual(context.codingPath, [-1]) + guard let int = any as? Int else { + XCTFail() + return + } + XCTAssertEqual(int, -1) + } } - func testIntegerKeysLowerBound() throws { + func testIntegerKeysValidLowerBound() throws { struct TestLowBound: Codable, Equatable { let val: Bool enum CodingKeys: Int, CodingKey { - case val = -576460752303423488 + case val = 0 } } let value = TestLowBound(val: true) let expected: [UInt8] = [ - 0b10000000, // Int key, varint, three LSB of int key - 128, 128, 128, 128, 128, 128, 128, 128, - 1, /// Bool `true` + 0, // Int key 0 + 2, 1, /// Bool `true` ] try compare(value, to: expected) } - func testIntegerKeysHighBound() throws { - struct TestHighBound: Codable, Equatable { + func testIntegerKeysValidUpperBound() throws { + struct TestUpperBound: Codable, Equatable { let val: Bool enum CodingKeys: Int, CodingKey { - case val = 576460752303423487 + case val = 9223372036854775807 } } - let value = TestHighBound(val: true) + let value = TestUpperBound(val: true) let expected: [UInt8] = [ - 0b11110000, // Int key, varint, three LSB of int key - 255, 255, 255, 255, 255, 255, 255, 127, - 1, /// Bool `true` + 254, 255, 255, 255, 255, 255, 255, 255, 255, // Int key 9223372036854775807 + 2, 1, /// Bool `true` ] try compare(value, to: expected) } - + func testSortingStructKeys() throws { struct Test: Codable, Equatable { - + let one: Int - + let two: String - + let three: Bool - + enum CodingKeys: Int, CodingKey { case one = 1 case two = 2 case three = 3 } } - + let val = Test(one: 123, two: "Some", three: true) try compare(val, to: [ - 0x10, // Int key(1), VarInt + 2, // Int key 1 + 4, // Length 2 246, 1, // Int(123) - 0x22, // Int key(2), VarLen - 4, // Length 4 - 83, 111, 109, 101, // String "Some" - 0x30, // Int key(3), VarInt - 1, // Bool(true) - ], sort: true) + 4, // Int key 2 + 8, // Length 4 + 83, 111, 109, 101, // "Some" + 6, // Int key 3 + 2, // Length 1 + 1, // 'true' + ], sortingKeys: true) } - + func testDecodeDictionaryAsStruct() throws { struct Test: Codable, Equatable { let a: Int let b: Int let c: Int } - + let input: [String: Int] = ["a" : 123, "b": 0, "c": -123456] let encoded = try BinaryEncoder.encode(input) - + let decoded: Test = try BinaryDecoder.decode(from: encoded) XCTAssertEqual(decoded, Test(a: 123, b: 0, c: -123456)) } - + func testDecodeStructAsDictionary() throws { struct Test: Codable, Equatable { let a: Int let b: Int let c: Int } - + let input = Test(a: 123, b: 0, c: -123456) let encoded = try BinaryEncoder.encode(input) - + let decoded: [String: Int] = try BinaryDecoder.decode(from: encoded) XCTAssertEqual(decoded, ["a" : 123, "b": 0, "c": -123456]) } @@ -177,9 +196,10 @@ final class StructEncodingTests: XCTestCase { } let expected: [UInt8] = [ - 0b00111010, 118, 97, 108, // String key 'val', varint - 4, // Length 4 - 83, 111, 109, 101, // String "Some" + 7, // String key, length 3 + 118, 97, 108, // "val" + 8, // Length 4 + 83, 111, 109, 101, // "Some" ] let wrapped = Wrapped(val: "Some") @@ -193,7 +213,8 @@ final class StructEncodingTests: XCTestCase { let wrapper = Wrapper(wrapped: wrapped) let encodedWrapper = try BinaryEncoder.encode(wrapper) - try compare(encodedWrapper, to: expected) + // Prepend nil-indicator + try compare(encodedWrapper, to: [0] + expected) let decodedWrapper: Wrapper = try BinaryDecoder.decode(from: encodedWrapper) XCTAssertEqual(decodedWrapper, wrapper) diff --git a/Tests/BinaryCodableTests/SuperEncodingTests.swift b/Tests/BinaryCodableTests/SuperEncodingTests.swift index 63f7909..8d368cd 100644 --- a/Tests/BinaryCodableTests/SuperEncodingTests.swift +++ b/Tests/BinaryCodableTests/SuperEncodingTests.swift @@ -88,30 +88,34 @@ final class SuperEncodingTests: XCTestCase { func testSuperEncodingWithDefaultKey() throws { let value = Child1(other: true, value: 123) let part1: [UInt8] = [ - 32, // Int key, varint, Key "2" + 4, // Int key 2 + 2, // Length 1 1, // Bool true ] let part2: [UInt8] = [ - 2, // VarLen key, varint, Key "0" - 3, // Length(3) - 16, // Int key, varint, Key "1" + 0, // Int key 0 + 8, // Length 4 + 2, // Int key 1 + 4, // Length 2 246, 1, // ZigZag(123) ] - try compare(value, possibleResults: [part1 + part2, part2 + part1]) + try compare(value, toOneOf: [part1 + part2, part2 + part1]) } func testSuperEncodingWithCustomKey() throws { let value = Child2(other: true, value: 123) let part1: [UInt8] = [ - 32, // Int key, varint, Key "2" + 4, // Int key 2 + 2, // Length 1 1, // Bool true ] let part2: [UInt8] = [ - 50, // VarLen key, varint, Key "3" - 3, // Length(3) - 16, // Int key, varint, Key "1" + 6, // Int key 3 + 8, // Length 4 + 2, // Int key 1 + 4, // Length 2 246, 1, // ZigZag(123) ] - try compare(value, possibleResults: [part1 + part2, part2 + part1]) + try compare(value, toOneOf: [part1 + part2, part2 + part1]) } } diff --git a/Tests/BinaryCodableTests/UUIDEncodingTests.swift b/Tests/BinaryCodableTests/UUIDEncodingTests.swift index 32a07e0..108db05 100644 --- a/Tests/BinaryCodableTests/UUIDEncodingTests.swift +++ b/Tests/BinaryCodableTests/UUIDEncodingTests.swift @@ -5,18 +5,20 @@ final class UUIDEncodingTests: XCTestCase { func testUUID() throws { let id = UUID(uuidString: "D0829408-FA77-4511-ACFC-21504DE16CE1")! - let expected = Array(id.uuidString.data(using: .utf8)!) + // Add nil indicator + let expected = [0] + Array(id.uuidString.data(using: .utf8)!) try compare(id, to: expected) } func testEnumWithUUID() throws { let id = UUID(uuidString: "D0829408-FA77-4511-ACFC-21504DE16CE1")! let value = UUIDContainer.test(id) - let expected = [74, 116, 101, 115, 116, // "test" - 40, // Length 40 - 42, // String key(2), varLength - 95, 48, // String "_0" - 36] // Length of UUID + let expected = [9, // String key, length 4 + 116, 101, 115, 116, // "test" + 80, // Length 40 + 5, // String key, length 2 + 95, 48, // "_0" + 72] // Length of UUID + Array(id.uuidString.data(using: .utf8)!) try compare(value, to: expected) } diff --git a/Tests/BinaryCodableTests/UnkeyedContainerTests.swift b/Tests/BinaryCodableTests/UnkeyedContainerTests.swift new file mode 100644 index 0000000..2f4e3c9 --- /dev/null +++ b/Tests/BinaryCodableTests/UnkeyedContainerTests.swift @@ -0,0 +1,65 @@ +import XCTest +import BinaryCodable + +final class UnkeyedContainerTests: XCTestCase { + + func testCountAndIndexInUnkeyedContainer() throws { + GenericTestStruct.encode { encoder in + var container = encoder.unkeyedContainer() + try container.encode(true) + try container.encode("Some") + try container.encode(123) + } + + GenericTestStruct.decode { decoder in + var container = try decoder.unkeyedContainer() + if let count = container.count { + XCTAssertEqual(count, 3) + } else { + XCTFail("No count in unkeyed container") + } + XCTAssertEqual(container.currentIndex, 0) + XCTAssertEqual(container.isAtEnd, false) + + XCTAssertEqual(try container.decode(Bool.self), true) + XCTAssertEqual(container.currentIndex, 1) + XCTAssertEqual(container.isAtEnd, false) + + XCTAssertEqual(try container.decode(String.self), "Some") + XCTAssertEqual(container.currentIndex, 2) + XCTAssertEqual(container.isAtEnd, false) + + XCTAssertEqual(try container.decode(Int.self), 123) + XCTAssertEqual(container.currentIndex, 3) + XCTAssertEqual(container.isAtEnd, true) + } + try compare(GenericTestStruct()) + } + + func testIntSet() throws { + let value: Set = [1, 2, 3, 123, Int.max, Int.min] + try compare(value) + } + + func testSetOfStructs() throws { + struct Test: Codable, Hashable { + let value: String + } + let values: Set = [.init(value: "Some"), .init(value: "More"), .init(value: "Test")] + try compare(values) + } + + func testSetOfOptionals() throws { + let value: Set = [true, false, nil] + try compare(value) + } + + func testOptionalSet() throws { + let value: Set? = [true, false] + try compare(value) + + let value2: Set? = nil + try compare(value2) + } + +} diff --git a/Tests/BinaryCodableTests/UserInfoTests.swift b/Tests/BinaryCodableTests/UserInfoTests.swift new file mode 100644 index 0000000..b316d0b --- /dev/null +++ b/Tests/BinaryCodableTests/UserInfoTests.swift @@ -0,0 +1,38 @@ +import XCTest +import BinaryCodable + +final class UserInfoTests: XCTestCase { + + func testUserInfoAvailableInEncoderAndDecoder() throws { + let key = CodingUserInfoKey(rawValue: "SomeKey")! + let value = true + + GenericTestStruct.encode { encoder in + var container = encoder.singleValueContainer() + if let value = encoder.userInfo[key] as? Bool { + XCTAssertTrue(value) + } else { + XCTFail() + } + try container.encode(false) + } + + GenericTestStruct.decode { decoder in + let container = try decoder.singleValueContainer() + if let value = decoder.userInfo[key] as? Bool { + XCTAssertTrue(value) + } else { + XCTFail() + } + let decoded = try container.decode(Bool.self) + XCTAssertEqual(decoded, false) + } + + var encoder = BinaryEncoder() + encoder.userInfo[key] = value + let encoded = try encoder.encode(GenericTestStruct()) + var decoder = BinaryDecoder() + decoder.userInfo[key] = value + _ = try decoder.decode(GenericTestStruct.self, from: encoded) + } +} diff --git a/Tests/BinaryCodableTests/VariableLengthEncodingTests.swift b/Tests/BinaryCodableTests/VariableLengthEncodingTests.swift index b330b43..4a4ec1d 100644 --- a/Tests/BinaryCodableTests/VariableLengthEncodingTests.swift +++ b/Tests/BinaryCodableTests/VariableLengthEncodingTests.swift @@ -2,52 +2,46 @@ import XCTest @testable import BinaryCodable final class VariableLengthEncodingTests: XCTestCase { - - private func rountTrip(_ type: T.Type, value: T) throws where T: Codable, T: VariableLengthCodable, T: Equatable { + + func compare(varInt value: T, of: T.Type, to result: [UInt8]) throws where T: VariableLengthCodable { let data = value.variableLengthEncoding - let decoded = try type.init(fromVarint: data, path: []) + XCTAssertEqual(Array(data), result) + let decoded = try T(fromVarint: data, codingPath: []) XCTAssertEqual(decoded, value) } - - func testEncodeUInt64() { - func compare(_ value: UInt64, to result: [UInt8]) { - XCTAssertEqual(Array(value.variableLengthEncoding), result) - } - compare(0, to: [0]) - compare(123, to: [123]) - compare(.max, to: .init(repeating: 0xFF, count: 9)) - compare(123456, to: [0xC0, 0xC4, 0x07]) + + func testEncodeInt() throws { + try compare(varInt: 0, of: Int.self, to: [0]) + try compare(varInt: 123, of: Int.self, to: [123]) + // For max, all next-byte bits are set, and all other bits are also set, except for the 63rd + try compare(varInt: .max, of: Int.self, to: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]) + // For min, only the 63rd bit is set, so the first 8 bytes have only the next-byte bit set, + // and the last byte (which has no next-byte bit, has the highest bit set + try compare(varInt: .min, of: Int.self, to: [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80]) + // For -1, all data bits are set, and also all next-byte bits. + try compare(varInt: -1, of: Int.self, to: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) } - - func testEncodeInt() { - func compare(_ value: Int, to result: [UInt8]) { - XCTAssertEqual(Array(value.variableLengthEncoding), result) - } - compare(0, to: [0]) - compare(123, to: [123]) + + func testEncodeInt32() throws { + try compare(varInt: 0, of: Int32.self, to: [0]) + try compare(varInt: 123, of: Int32.self, to: [123]) // For max, all next-byte bits are set, and all other bits are also set, except for the 63rd - compare(.max, to: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]) + try compare(varInt: .max, of: Int32.self, to: [0xFF, 0xFF, 0xFF, 0xFF, 0x07]) // For min, only the 63rd bit is set, so the first 8 bytes have only the next-byte bit set, // and the last byte (which has no next-byte bit, has the highest bit set - compare(.min, to: [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80]) + try compare(varInt: .min, of: Int32.self, to: [0x80, 0x80, 0x80, 0x80, 0x08]) // For -1, all data bits are set, and also all next-byte bits. - compare(-1, to: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + try compare(varInt: -1, of: Int32.self, to: [0xFF, 0xFF, 0xFF, 0xFF, 0x0F]) } - - func testEncodeDecodeUInt64() throws { - func roundTrip(_ value: UInt64) throws { - let data = value.variableLengthEncoding - let decoded = try UInt64(fromVarint: data, path: []) - XCTAssertEqual(decoded, value) - } - - try roundTrip(0) - try roundTrip(.max) - try roundTrip(.max - 1) - try roundTrip(123) - try roundTrip(1234) - try roundTrip(123456) - try roundTrip(1234567890) - try roundTrip(1234567890123456) + + func testEncodeUInt64() throws { + try compare(varInt: 0, of: UInt64.self, to: [0]) + try compare(varInt: 123, of: UInt64.self, to: [123]) + try compare(varInt: 1234, of: UInt64.self, to: [0xD2, 0x09]) + try compare(varInt: 123456, of: UInt64.self, to: [0xC0, 0xC4, 0x07]) + try compare(varInt: 1234567890, of: UInt64.self, to: [0xD2, 0x85, 0xD8, 0xCC, 0x04]) + try compare(varInt: 1234567890123456, of: UInt64.self, to: [0xC0, 0xF5, 0xAA, 0xE4, 0xD3, 0xDA, 0x98, 0x02]) + try compare(varInt: .max - 1, of: UInt64.self, to: [0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + try compare(varInt: .max, of: UInt64.self, to: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) } } diff --git a/Tests/BinaryCodableTests/WrapperEncodingTests.swift b/Tests/BinaryCodableTests/WrapperEncodingTests.swift index 44a2dc3..c6377ab 100644 --- a/Tests/BinaryCodableTests/WrapperEncodingTests.swift +++ b/Tests/BinaryCodableTests/WrapperEncodingTests.swift @@ -1,60 +1,67 @@ import XCTest -import BinaryCodable +@testable import BinaryCodable final class WrapperEncodingTests: XCTestCase { + private func compareFixed(_ value: FixedSize, of type: T.Type, to expected: [UInt8]) throws where T: FixedSizeCodable, T: CodablePrimitive { + try compareEncoding(of: value, withType: FixedSize.self, isEqualTo: expected) + } + func testFixedSizeWrapperInt() throws { - func compare(_ value: FixedSize, to expected: [UInt8]) throws { - try compareEncoding(FixedSize.self, value: value, to: expected) - } - try compare(123, to: [123, 0, 0, 0, 0, 0, 0, 0]) - try compare(.max, to: [255, 255, 255, 255, 255, 255, 255, 127]) - try compare(.min, to: [0, 0, 0, 0, 0, 0, 0, 128]) + try compareFixed(123, of: Int.self, to: [123, 0, 0, 0, 0, 0, 0, 0]) + try compareFixed(.max, of: Int.self, to: [255, 255, 255, 255, 255, 255, 255, 127]) + try compareFixed(.min, of: Int.self, to: [0, 0, 0, 0, 0, 0, 0, 128]) } func testFixedSizeWrapperInt32() throws { - func compare(_ value: FixedSize, to expected: [UInt8]) throws { - try compareEncoding(FixedSize.self, value: value, to: expected) - } - try compare(123, to: [123, 0, 0, 0]) - try compare(.max, to: [255, 255, 255, 127]) - try compare(.min, to: [0, 0, 0, 128]) + try compareFixed(123, of: Int32.self, to: [123, 0, 0, 0]) + try compareFixed(.max, of: Int32.self, to: [255, 255, 255, 127]) + try compareFixed(.min, of: Int32.self, to: [0, 0, 0, 128]) } func testFixedSizeWrapperInt64() throws { - func compare(_ value: FixedSize, to expected: [UInt8]) throws { - try compareEncoding(FixedSize.self, value: value, to: expected) - } - try compare(123, to: [123, 0, 0, 0, 0, 0, 0, 0]) - try compare(.max, to: [255, 255, 255, 255, 255, 255, 255, 127]) - try compare(.min, to: [0, 0, 0, 0, 0, 0, 0, 128]) + try compareFixed(123, of: Int64.self, to: [123, 0, 0, 0, 0, 0, 0, 0]) + try compareFixed(.max, of: Int64.self, to: [255, 255, 255, 255, 255, 255, 255, 127]) + try compareFixed(.min, of: Int64.self, to: [0, 0, 0, 0, 0, 0, 0, 128]) } func testFixedSizeWrapperUInt32() throws { - func compare(_ value: FixedSize, to expected: [UInt8]) throws { - try compareEncoding(FixedSize.self, value: value, to: expected) - } - try compare(123, to: [123, 0, 0, 0]) - try compare(.max, to: [255, 255, 255, 255]) - try compare(.min, to: [0, 0, 0, 0]) + try compareFixed(123, of: UInt32.self, to: [123, 0, 0, 0]) + try compareFixed(.max, of: UInt32.self, to: [255, 255, 255, 255]) + try compareFixed(.min, of: UInt32.self, to: [0, 0, 0, 0]) } func testFixedSizeWrapperUInt64() throws { - func compare(_ value: FixedSize, to expected: [UInt8]) throws { - try compareEncoding(FixedSize.self, value: value, to: expected) - } - try compare(123, to: [123, 0, 0, 0, 0, 0, 0, 0]) - try compare(.max, to: [255, 255, 255, 255, 255, 255, 255, 255]) - try compare(.min, to: [0, 0, 0, 0, 0, 0, 0, 0]) + try compareFixed(123, of: UInt64.self, to: [123, 0, 0, 0, 0, 0, 0, 0]) + try compareFixed(.max, of: UInt64.self, to: [255, 255, 255, 255, 255, 255, 255, 255]) + try compareFixed(.min, of: UInt64.self, to: [0, 0, 0, 0, 0, 0, 0, 0]) } func testFixedSizeWrapperUInt() throws { - func compare(_ value: FixedSize, to expected: [UInt8]) throws { - try compareEncoding(FixedSize.self, value: value, to: expected) + try compareFixed(123, of: UInt.self, to: [123, 0, 0, 0, 0, 0, 0, 0]) + try compareFixed(.max, of: UInt.self, to: [255, 255, 255, 255, 255, 255, 255, 255]) + try compareFixed(.min, of: UInt.self, to: [0, 0, 0, 0, 0, 0, 0, 0]) + } + + func testFixedIntInStruct() throws { + struct Test: Codable, Equatable { + + @FixedSize + var val: Int } - try compare(123, to: [123, 0, 0, 0, 0, 0, 0, 0]) - try compare(.max, to: [255, 255, 255, 255, 255, 255, 255, 255]) - try compare(.min, to: [0, 0, 0, 0, 0, 0, 0, 0]) + try compare(Test(val: 123), to: [ + 7, // String key, length 3 + 118, 97, 108, // "val" + 16, // Length 8 + 123, 0, 0, 0, 0, 0, 0, 0 // '123' + ]) + + try compare(Test(val: -123), to: [ + 7, // String key, length 3 + 118, 97, 108, // "val" + 16, // Length 8 + 133, 255, 255, 255, 255, 255, 255, 255 // '-123' + ]) } func testFixedInt32InStruct() throws { @@ -63,8 +70,19 @@ final class WrapperEncodingTests: XCTestCase { @FixedSize var val: Int32 } - try compare(Test(val: 123), to: [0b00111101, 118, 97, 108, - 123, 0, 0, 0]) + try compare(Test(val: 123), to: [ + 7, // String key, length 3 + 118, 97, 108, // "val" + 8, // Length 4 + 123, 0, 0, 0 // '123' + ]) + + try compare(Test(val: -123), to: [ + 7, // String key, length 3 + 118, 97, 108, // "val" + 8, // Length 4 + 133, 255, 255, 255 // '-123' + ]) } func testFixedInt64InStruct() throws { @@ -73,8 +91,19 @@ final class WrapperEncodingTests: XCTestCase { @FixedSize var val: Int64 } - try compare(Test(val: 123), to: [0b00111001, 118, 97, 108, - 123, 0, 0, 0, 0, 0, 0, 0]) + try compare(Test(val: 123), to: [ + 7, // String key, length 3 + 118, 97, 108, // "val" + 16, // Length 8 + 123, 0, 0, 0, 0, 0, 0, 0 // '123' + ]) + + try compare(Test(val: -123), to: [ + 7, // String key, length 3 + 118, 97, 108, // "val" + 16, // Length 8 + 133, 255, 255, 255, 255, 255, 255, 255 // '-123' + ]) } func testFixedUInt32InStruct() throws { @@ -83,8 +112,12 @@ final class WrapperEncodingTests: XCTestCase { @FixedSize var val: UInt32 } - try compare(Test(val: 123), to: [0b00111101, 118, 97, 108, - 123, 0, 0, 0]) + try compare(Test(val: 123), to: [ + 7, // String key, length 3 + 118, 97, 108, // "val" + 8, // Length 4 + 123, 0, 0, 0 // '123' + ]) } func testFixedUInt64InStruct() throws { @@ -93,7 +126,11 @@ final class WrapperEncodingTests: XCTestCase { @FixedSize var val: UInt64 } - try compare(Test(val: 123), to: [0b00111001, 118, 97, 108, - 123, 0, 0, 0, 0, 0, 0, 0]) + try compare(Test(val: 123), to: [ + 7, // String key, length 3 + 118, 97, 108, // "val" + 16, // Length 8 + 123, 0, 0, 0, 0, 0, 0, 0 // '123' + ]) } } diff --git a/docs/Classes.html b/docs/Classes.html deleted file mode 100644 index 3e09aed..0000000 --- a/docs/Classes.html +++ /dev/null @@ -1,548 +0,0 @@ - - - - Classes Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Classes

-

The following classes are available globally.

- -
-
-
-
    -
  • -
    - - - - BinaryDecoder - -
    -
    -
    -
    -
    -
    -

    An encoder to convert binary data back to Codable objects.

    - -

    To decode from data, instantiate a decoder and specify the type:

    -
    let decoder = BinaryDecoder()
    -let message = try decoder.decode(Message.self, from: data)
    -
    - -

    Alternatively, the type can be inferred from context:

    -
    func decode(data: Data) throws -> Message {
    -    try BinaryDecoder().decode(from: data)
    -}
    -
    - -

    There are also convenience functions to directly decode a single instance:

    -
    let message = try BinaryDecoder.decode(Message.self, from: data)
    -
    -
    -

    Note

    - A single decoder can be used to decode multiple messages. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryDecoder
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryEncoder - -
    -
    -
    -
    -
    -
    -

    An encoder to convert Codable objects to binary data.

    - -

    Construct an encoder when converting instances to binary data, and feed the message(s) into it:

    -
    let message: Message = ...
    -
    -let encoder = BinaryEncoder()
    -let data = try encoder.encode(message)
    -
    -
    -

    Note

    - An ecoder can be used to encode multiple messages. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryEncoder
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryFileDecoder - -
    -
    -
    -
    -
    -
    -

    Read elements from a binary file.

    - -

    The decoder allows reading individual elements from a file without loading all file data to memory all at once. -This decreases memory usage, which is especially useful for large files. -Elements can also be read all at once, and corrupted files can be read until the first decoding error occurs.

    - -

    The class internally uses BinaryStreamDecoder to encode the individual elements, -which can also be used independently to decode the data for more complex operations.

    - -

    Handling corrupted data

    - -

    The binary format does not necessarily allow detection of data corruption, and various errors can occur -as the result of added, changed, or missing bytes. Additional measures should be applied if there is an -increased risk of data corruption.

    - -

    As an example, consider the simple encoding of a String inside a struct, which consists of a key -followed by the length of the string in bytes, and the string content. The length of the string is encoded using -variable length encoding, so a single bit flip (in the MSB of the length byte) could result in a very large length being decoded, -causing the decoder to wait for a very large number of bytes to decode the string. This simple error would cause much -data to be skipped. At the same time, it is not possible to determine with certainty where the error occured.

    - -

    The library does therefore only provide hints about the decoding errors likely occuring from non-conformance to the binary format -or version incompatibility, which are not necessarily the true causes of the failures when data corruption is present.

    -
    -

    Note

    - This class is compatible with BinaryFileEncoder and BinaryStreamEncoder, -but not with the outputs of BinaryEncoder. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryFileDecoder<Element> where Element : Decodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryFileEncoder - -
    -
    -
    -
    -
    -
    -

    Encode a stream of elements to a binary file.

    - -

    This class complements BinaryStreamEncoder to directly write encoded elements to a file.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryFileEncoder<Element> where Element : Encodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryStreamDecoder - -
    -
    -
    -
    -
    -
    -

    Decode elements from a byte stream.

    - -

    Stream decoding can be used when either the data is not regularly available completely (e.g. when loading data over a network connection), -or when the binary data should not be loaded into memory all at once (e.g. when parsing a file).

    - -

    Each stream decoder handles only elements of a single type. -The decoder can then attempt to read elements whenever new data is received, until a complete element can be decoded. -Buffering is handled internally, freeing the stream provider from this responsibility.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryStreamDecoder<Element> where Element : Decodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryStreamEncoder - -
    -
    -
    -
    -
    -
    -

    Encode elements sequentially into a binary data stream.

    - -

    A stream encoder is used to encode individual elements of the same type to a continuous binary stream, -which can be decoded sequentially.

    - -

    The encoding behaviour is different to BinaryEncoder, where the full data must be present to successfully decode. -Additional information is embedded into the stream to facilitate this behaviour. -The binary data produced by a stream encoder is not compatible with BinaryDecoder and can only be decoded using -BinaryStreamDecoder.

    - -

    The special data format of an encoded stream also allows joining sequences of encoded data, where: -encode([a,b]) + encode([c,d]) == encode([a,b,c,d]) and decode(encode([a]) + encode([b])) == [a,b]

    - -

    Example:

    -
    let encoder = BinaryStreamEncoder<Int>()
    -let encoded1 = try encoder.encode(1)
    -
    -let decoder = BinaryStreamDecoder<Int>()
    -let decoded1 = try decoder.decode(encoded1)
    -print(decoded1) // [1]
    -
    -let encoded2 = try encoder.encode(contentsOf: [2,3])
    -let decoded2 = try decoder.decode(encoded2)
    -print(decoded2) // [2,3]
    -
    -
    -

    Note

    - Stream decoders always work on a single type, because no type information is encoded into the data. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryStreamEncoder<Element> where Element : Encodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtobufDecoder - -
    -
    -
    -
    -
    -
    -

    An encoder to convert protobuf binary data back to Codable objects.

    - -

    Decoding unsupported data types causes DecodingError or ProtobufDecodingError errors.

    - -

    To decode from data, instantiate a decoder and specify the type:

    -
    let decoder = BinaryDecoder()
    -let message = try decoder.decode(Message.self, from: data)
    -
    - -

    Alternatively, the type can be inferred from context:

    -
    func decode(data: Data) throws -> Message {
    -    try BinaryDecoder().decode(from: data)
    -}
    -
    - -

    There are also convenience functions to directly decode a single instance:

    -
    let message = try BinaryDecoder.decode(Message.self, from: data)
    -
    -
    -

    Note

    - A single decoder can be used to decode multiple messages. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class ProtobufDecoder
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtobufEncoder - -
    -
    -
    -
    -
    -
    -

    An encoder to convert Codable objects to protobuf binary data.

    - -

    The encoder provides only limited compatibility with Google’s Protocol Buffers.

    - -

    Encoding unsupported data types causes ProtobufEncodingError errors.

    - -

    Construct an encoder when converting instances to binary data, and feed the message(s) into it:

    -
    let message: Message = ...
    -
    -let encoder = ProtobufEncoder()
    -let data = try encoder.encode(message)
    -
    -
    -

    Note

    - An ecoder can be used to encode multiple messages. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class ProtobufEncoder
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Classes/BinaryDecoder.html b/docs/Classes/BinaryDecoder.html deleted file mode 100644 index 1265412..0000000 --- a/docs/Classes/BinaryDecoder.html +++ /dev/null @@ -1,449 +0,0 @@ - - - - BinaryDecoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryDecoder

-
-
- -
public final class BinaryDecoder
- -
-
-

An encoder to convert binary data back to Codable objects.

- -

To decode from data, instantiate a decoder and specify the type:

-
let decoder = BinaryDecoder()
-let message = try decoder.decode(Message.self, from: data)
-
- -

Alternatively, the type can be inferred from context:

-
func decode(data: Data) throws -> Message {
-    try BinaryDecoder().decode(from: data)
-}
-
- -

There are also convenience functions to directly decode a single instance:

-
let message = try BinaryDecoder.decode(Message.self, from: data)
-
-
-

Note

- A single decoder can be used to decode multiple messages. - -
- -
-
-
-
    -
  • -
    - - - - userInfo - -
    -
    -
    -
    -
    -
    -

    Any contextual information set by the user for decoding.

    - -

    This dictionary is passed to all containers during the decoding process.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var userInfo: [CodingUserInfoKey : Any]
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Assumes that unkeyed containers are encoded using a set of indices for nil values.

    - -

    Refer to the prependNilIndexSetForUnkeyedContainers property of BinaryEncoder -for more information about the binary data format in both cases.

    -
    -

    Note

    - This option defaults to false - -
    -

    Note

    - To decode successfully, the encoder must use the same setting for prependNilIndexSetForUnkeyedContainers. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var containsNilIndexSetForUnkeyedContainers: Bool
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Create a new binary encoder.

    -
    -

    Note

    - A single decoder can be used to decode multiple messages. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - decode(_:from:) - -
    -
    -
    -
    -
    -
    -

    Decode a type from binary data.

    -
    -

    Throws

    - Errors of type DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decode<T>(_ type: T.Type = T.self, from data: Data) throws -> T where T : Decodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - type - - -
    -

    The type to decode.

    -
    -
    - - data - - -
    -

    The binary data which encodes the instance

    -
    -
    -
    -
    -

    Return Value

    -

    The decoded instance

    -
    -
    -
    -
  • -
  • -
    - - - - decode(_:from:) - -
    -
    -
    -
    -
    -
    -

    Decode a single value from binary data using a default decoder.

    -
    -

    Throws

    - Errors of type DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func decode<T>(_ type: T.Type = T.self, from data: Data) throws -> T where T : Decodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - type - - -
    -

    The type to decode.

    -
    -
    - - data - - -
    -

    The binary data which encodes the instance

    -
    -
    -
    -
    -

    Return Value

    -

    The decoded instance

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Classes/BinaryEncoder.html b/docs/Classes/BinaryEncoder.html deleted file mode 100644 index 0cf577f..0000000 --- a/docs/Classes/BinaryEncoder.html +++ /dev/null @@ -1,477 +0,0 @@ - - - - BinaryEncoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryEncoder

-
-
- -
public final class BinaryEncoder
- -
-
-

An encoder to convert Codable objects to binary data.

- -

Construct an encoder when converting instances to binary data, and feed the message(s) into it:

-
let message: Message = ...
-
-let encoder = BinaryEncoder()
-let data = try encoder.encode(message)
-
-
-

Note

- An ecoder can be used to encode multiple messages. - -
- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    Sort keyed data in the binary representation.

    - -

    Enabling this option causes all keyed data (e.g. Dictionary, Struct) to be sorted by their keys before encoding. -This option can enable deterministic encoding where the binary output is consistent across multiple invocations.

    -
    -

    Warning

    - Output will not be deterministic when using Set, or Dictionary<Key, Value> where Key is not String or Int. - -
    - -

    Enabling this option introduces computational overhead due to sorting, which can become significant when dealing with many entries.

    - -

    This option has no impact on decoding using BinaryDecoder.

    - -

    Enabling this option will add the CodingUserInfoKey(rawValue: "sort") to the userInfo dictionary.

    -
    -

    Note

    - The default value for this option is false. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var sortKeysDuringEncoding: Bool
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Add a set of indices for nil values in unkeyed containers.

    - -

    This option is only necessary for custom implementations of func encode(to:), when using encodeNil() on UnkeyedEncodingContainer.

    - -

    If this option is set to true, then the encoded binary data first contains a list of indexes for each position where nil is encoded. -After this data the remaining (non-nil) values are added. -If this option is false, then each value is prepended with a byte 1 for non-nil values, and a byte 0 for nil values.

    - -

    An index set is encoded using first the number of elements, and then each element, all encoded as var-ints.

    - -

    One benefit of this option is that top-level sequences can be joined using their binary data, where encoded([a,b]) | encoded([c,d]) == encoded([a,b,c,d]).

    -
    -

    Note

    - This option defaults to false - -
    -

    Note

    - To decode successfully, the decoder must use the same setting for containsNilIndexSetForUnkeyedContainers. - -
    -

    Note

    - Using encodeNil() on UnkeyedEncodingContainer without this option results in a fatal error. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var prependNilIndexSetForUnkeyedContainers: Bool
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - userInfo - -
    -
    -
    -
    -
    -
    -

    Any contextual information set by the user for encoding.

    - -

    This dictionary is passed to all containers during the encoding process.

    - -

    Contains also keys for any custom options set for the encoder. -See sortKeysDuringEncoding.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var userInfo: [CodingUserInfoKey : Any]
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Create a new binary encoder.

    -
    -

    Note

    - An encoder can be used to encode multiple messages. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode a value to binary data.

    -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode(_ value: Encodable) throws -> Data
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - value - - -
    -

    The value to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The encoded data

    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode a single value to binary data using a default encoder.

    -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func encode(_ value: Encodable) throws -> Data
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - value - - -
    -

    The value to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The encoded data

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Classes/BinaryFileDecoder.html b/docs/Classes/BinaryFileDecoder.html deleted file mode 100644 index b40ae09..0000000 --- a/docs/Classes/BinaryFileDecoder.html +++ /dev/null @@ -1,495 +0,0 @@ - - - - BinaryFileDecoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryFileDecoder

-
-
- -
public final class BinaryFileDecoder<Element> where Element : Decodable
- -
-
-

Read elements from a binary file.

- -

The decoder allows reading individual elements from a file without loading all file data to memory all at once. -This decreases memory usage, which is especially useful for large files. -Elements can also be read all at once, and corrupted files can be read until the first decoding error occurs.

- -

The class internally uses BinaryStreamDecoder to encode the individual elements, -which can also be used independently to decode the data for more complex operations.

- -

Handling corrupted data

- -

The binary format does not necessarily allow detection of data corruption, and various errors can occur -as the result of added, changed, or missing bytes. Additional measures should be applied if there is an -increased risk of data corruption.

- -

As an example, consider the simple encoding of a String inside a struct, which consists of a key -followed by the length of the string in bytes, and the string content. The length of the string is encoded using -variable length encoding, so a single bit flip (in the MSB of the length byte) could result in a very large length being decoded, -causing the decoder to wait for a very large number of bytes to decode the string. This simple error would cause much -data to be skipped. At the same time, it is not possible to determine with certainty where the error occured.

- -

The library does therefore only provide hints about the decoding errors likely occuring from non-conformance to the binary format -or version incompatibility, which are not necessarily the true causes of the failures when data corruption is present.

-
-

Note

- This class is compatible with BinaryFileEncoder and BinaryStreamEncoder, -but not with the outputs of BinaryEncoder. - -
- -
-
-
-
    -
  • -
    - - - - init(fileAt:decoder:) - -
    -
    -
    -
    -
    -
    -

    Create a file decoder.

    - -

    The given file is opened, and decoding will begin at the start of the file.

    -
    -

    Throws

    - An error, if the file handle could not be created. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fileAt url: URL, decoder: BinaryDecoder = .init()) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - url - - -
    -

    The url of the file to read.

    -
    -
    - - decoder - - -
    -

    The decoder to use for decoding

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    Close the file.

    -
    -

    Note

    - After closing the file, the decoder can no longer read elements, which will result in an error or an exception. - -
    -

    Throws

    - Currently throws a ObjC-style Exception, not an Error, even on modern systems. -This is a bug in the Foundation framework. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close() throws
    - -
    -
    -
    -
    -
  • -
-
-
-
- - -
- -

Decoding -

-
-
-
    -
  • -
    - - - - read(_:) - -
    -
    -
    -
    -
    -
    -

    Read all elements in the file, and handle each element using a closure.

    -
    -

    Throws

    - Decoding errors of type DecodingError. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func read(_ elementHandler: (Element) throws -> Void) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - elementHandler - - -
    -

    The closure to handle each element as it is decoded.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - readAll() - -
    -
    -
    -
    -
    -
    -

    Read all elements at once.

    -
    -

    Throws

    - Errors of type DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func readAll() throws -> [Element]
    - -
    -
    -
    -

    Return Value

    -

    The elements decoded from the file.

    -
    -
    -
    -
  • -
  • -
    - - - - readAllUntilError() - -
    -
    -
    -
    -
    -
    -

    Read all elements at once, and ignore errors.

    - -

    This function reads elements until it reaches the end of the file or detects a decoding error. -Any data after the first error will be ignored.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func readAllUntilError() -> [Element]
    - -
    -
    -
    -

    Return Value

    -

    The elements successfully decoded from the file.

    -
    -
    -
    -
  • -
  • -
    - - - - readElement() - -
    -
    -
    -
    -
    -
    -

    Read a single elements from the current position in the file.

    -
    -

    Throws

    - Errors of type DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func readElement() throws -> Element?
    - -
    -
    -
    -

    Return Value

    -

    The element decoded from the file, or nil, if no more data is available.

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Classes/BinaryFileEncoder.html b/docs/Classes/BinaryFileEncoder.html deleted file mode 100644 index 1adea8b..0000000 --- a/docs/Classes/BinaryFileEncoder.html +++ /dev/null @@ -1,410 +0,0 @@ - - - - BinaryFileEncoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryFileEncoder

-
-
- -
public final class BinaryFileEncoder<Element> where Element : Encodable
- -
-
-

Encode a stream of elements to a binary file.

- -

This class complements BinaryStreamEncoder to directly write encoded elements to a file.

- -
-
-
-
    -
  • -
    - - - - init(fileAt:encoder:) - -
    -
    -
    -
    -
    -
    -

    Create a new file encoder.

    -
    -

    Note

    - The file will be created, if it does not exist. -If it exists, then the new elements will be appended to the end of the file. - -
    -

    Throws

    - Throws an error if the file could not be accessed or created. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fileAt url: URL, encoder: BinaryEncoder = .init()) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - url - - -
    -

    The url to the file.

    -
    -
    - - encoder - - -
    -

    The encoder to use for each element.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    Close the file.

    -
    -

    Note

    - After closing the file, the encoder can no longer write elements, which will result in an error or an exception. - -
    -

    Throws

    - Currently throws a ObjC-style Exception, not an Error, even on modern systems. -This is a bug in the Foundation framework. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close() throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - write(_:) - -
    -
    -
    -
    -
    -
    -

    Write a single element to the file.

    -
    -

    Note

    - This function will throw an error or exception if the file handle has already been closed. - -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func write(_ element: Element) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - element - - -
    -

    The element to encode.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - write(contentsOf:) - -
    -
    -
    -
    -
    -
    -

    Write a sequence of elements to the file.

    - -

    This is a convenience function calling write(_ element:) for each element of the sequence in order.

    -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func write<S>(contentsOf sequence: S) throws where Element == S.Element, S : Sequence
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - sequence - - -
    -

    The sequence to encode

    -
    -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Classes/BinaryStreamDecoder.html b/docs/Classes/BinaryStreamDecoder.html deleted file mode 100644 index cc98207..0000000 --- a/docs/Classes/BinaryStreamDecoder.html +++ /dev/null @@ -1,535 +0,0 @@ - - - - BinaryStreamDecoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryStreamDecoder

-
-
- -
public final class BinaryStreamDecoder<Element> where Element : Decodable
- -
-
-

Decode elements from a byte stream.

- -

Stream decoding can be used when either the data is not regularly available completely (e.g. when loading data over a network connection), -or when the binary data should not be loaded into memory all at once (e.g. when parsing a file).

- -

Each stream decoder handles only elements of a single type. -The decoder can then attempt to read elements whenever new data is received, until a complete element can be decoded. -Buffering is handled internally, freeing the stream provider from this responsibility.

- -
-
-
-
    -
  • -
    - - - - init(decoder:) - -
    -
    -
    -
    -
    -
    -

    Create a stream decoder.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(decoder: BinaryDecoder = .init())
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - decoder - - -
    -

    The decoder to use for decoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - add(_:) - -
    -
    -
    -
    -
    -
    -

    Add new data to the internal buffer without attempting to decode elements.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func add(_ data: Data)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - data - - -
    -

    The datat to append to the internal buffer.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - discardBytes(_:) - -
    -
    -
    -
    -
    -
    -

    Discard bytes from the currently remaining bytes.

    - -

    This function can be used when attempting to correct a corrupted stream. It may be possible to drop bytes from the buffer until a new valid element can be decoded.

    -
    -

    Note

    - If the stream data is corrupted, then decoding elements from the middle of the stream may lead to unexpected results. -It’s possible to decode bogus elements when starting at the wrong position, or the stream decoder could get stuck waiting for a large number of bytes. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    @discardableResult
    -public func discardBytes(_ count: Int) -> Int
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - count - - -
    -

    The number of bytes to remove

    -
    -
    -
    -
    -

    Return Value

    -

    The number of bytes removed.

    -
    -
    -
    -
  • -
-
-
-
- - -
- -

Decoding -

-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    Read elements from the stream until no more bytes are available.

    -
    -

    Throws

    - Decoding errors of type DecodingError. - -
    -

    Note

    - This function is deprecated. If you want to decode elements, use decodeElements(), or use decodeElementsUntilError(), if you expect errors in the data stream. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    @available(*, deprecated, message: "Use add(﹚ and decodeElementsUntilError(﹚ or decodeElements(﹚ instead.")
    -public func decode(_ data: Data, returnElementsBeforeError: Bool = false) throws -> [Element]
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - returnElementsBeforeError - - -
    -

    If set to true, -then all successfully decoded elements will be returned if an error occurs. Defaults to false

    -
    -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Read elements from the stream until an error occurs.

    - -

    This function may be useful if the data is corrupted. The readable elements can be decoded until a decoding error is encountered.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decodeElementsUntilError() -> (elements: [Element], error: Error?)
    - -
    -
    -
    -

    Return Value

    -

    The elements that could be decoded until an error occured or the buffer was insufficient, and the decoding error, if one occured.

    -
    -
    -
    -
  • -
  • -
    - - - - decodeElements() - -
    -
    -
    -
    -
    -
    -

    Read elements until no more data is available.

    -
    -

    Note

    - If a decoding error occurs within the data, then the elements successfully decoded before are lost. -If you expect decoding errors to occur, either decode elements individually using decodeElement(), or use decodeElementsUntilError(). - -
    -

    Throws

    - Decoding errors of type DecodingError. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decodeElements() throws -> [Element]
    - -
    -
    -
    -

    Return Value

    -

    The elements that could be decoded with the available data.

    -
    -
    -
    -
  • -
  • -
    - - - - decodeElement() - -
    -
    -
    -
    -
    -
    -

    Attempt to decode a single element from the stream.

    - -

    If insufficient bytes are available, then no element is returned, and the internal buffer is kept intact.

    -
    -

    Throws

    - Decoding errors of type DecodingError. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decodeElement() throws -> Element?
    - -
    -
    -
    -

    Return Value

    -

    The decoded element, if enough bytes are available.

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Classes/BinaryStreamEncoder.html b/docs/Classes/BinaryStreamEncoder.html deleted file mode 100644 index 31dd421..0000000 --- a/docs/Classes/BinaryStreamEncoder.html +++ /dev/null @@ -1,399 +0,0 @@ - - - - BinaryStreamEncoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryStreamEncoder

-
-
- -
public final class BinaryStreamEncoder<Element> where Element : Encodable
- -
-
-

Encode elements sequentially into a binary data stream.

- -

A stream encoder is used to encode individual elements of the same type to a continuous binary stream, -which can be decoded sequentially.

- -

The encoding behaviour is different to BinaryEncoder, where the full data must be present to successfully decode. -Additional information is embedded into the stream to facilitate this behaviour. -The binary data produced by a stream encoder is not compatible with BinaryDecoder and can only be decoded using -BinaryStreamDecoder.

- -

The special data format of an encoded stream also allows joining sequences of encoded data, where: -encode([a,b]) + encode([c,d]) == encode([a,b,c,d]) and decode(encode([a]) + encode([b])) == [a,b]

- -

Example:

-
let encoder = BinaryStreamEncoder<Int>()
-let encoded1 = try encoder.encode(1)
-
-let decoder = BinaryStreamDecoder<Int>()
-let decoded1 = try decoder.decode(encoded1)
-print(decoded1) // [1]
-
-let encoded2 = try encoder.encode(contentsOf: [2,3])
-let decoded2 = try decoder.decode(encoded2)
-print(decoded2) // [2,3]
-
-
-

Note

- Stream decoders always work on a single type, because no type information is encoded into the data. - -
- -
-
-
-
    -
  • -
    - - - - init(encoder:) - -
    -
    -
    -
    -
    -
    -

    Create a new stream encoder.

    -
    -

    Note

    - The encoder should never be reconfigured after being passed to this function, -to prevent decoding errors due to mismatching binary formats. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(encoder: BinaryEncoder = .init())
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - encoder - - -
    -

    The encoder to use for the individual elements.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode an element for the data stream.

    - -

    Call this function to convert an element into binary data whenever new elements are available. -The data provided as the result of this function should be processed (e.g. stored or transmitted) while conserving the -order of the chunks, so that decoding can work reliably.

    - -

    Pass the encoded data back into an instance of BinaryStreamDecoder to convert each chunk back to an element.

    -
    -

    Note

    - Data encoded by this function can only be decoded by an appropriate BinaryStreamDecoder. -Decoding using a simple BinaryDecoder will not be successful. - -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode(_ element: Element) throws -> Data
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - element - - -
    -

    The element to encode.

    -
    -
    -
    -
    -

    Return Value

    -

    The next chunk of the encoded binary stream.

    -
    -
    -
    -
  • -
  • -
    - - - - encode(contentsOf:) - -
    -
    -
    -
    -
    -
    -

    Encode a sequence of elements.

    - -

    This function performs multiple calls to encode(_:) to convert all elements of the sequence, and then returns the joined data.

    -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode<S>(contentsOf sequence: S) throws -> Data where Element == S.Element, S : Sequence
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - sequence - - -
    -

    The sequence of elements to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The binary data of the encoded sequence elements

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Classes/ProtobufDecoder.html b/docs/Classes/ProtobufDecoder.html deleted file mode 100644 index b0deaeb..0000000 --- a/docs/Classes/ProtobufDecoder.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - ProtobufDecoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

ProtobufDecoder

-
-
- -
public final class ProtobufDecoder
- -
-
-

An encoder to convert protobuf binary data back to Codable objects.

- -

Decoding unsupported data types causes DecodingError or ProtobufDecodingError errors.

- -

To decode from data, instantiate a decoder and specify the type:

-
let decoder = BinaryDecoder()
-let message = try decoder.decode(Message.self, from: data)
-
- -

Alternatively, the type can be inferred from context:

-
func decode(data: Data) throws -> Message {
-    try BinaryDecoder().decode(from: data)
-}
-
- -

There are also convenience functions to directly decode a single instance:

-
let message = try BinaryDecoder.decode(Message.self, from: data)
-
-
-

Note

- A single decoder can be used to decode multiple messages. - -
- -
-
-
-
    -
  • -
    - - - - userInfo - -
    -
    -
    -
    -
    -
    -

    Any contextual information set by the user for decoding.

    - -

    This dictionary is passed to all containers during the decoding process.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var userInfo: [CodingUserInfoKey : Any]
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Create a new binary encoder.

    -
    -

    Note

    - A single decoder can be used to decode multiple messages. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - decode(_:from:) - -
    -
    -
    -
    -
    -
    -

    Decode a type from binary data.

    -
    -

    Throws

    - Errors of type DecodingError or ProtobufDecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decode<T>(_ type: T.Type = T.self, from data: Data) throws -> T where T : Decodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - type - - -
    -

    The type to decode.

    -
    -
    - - data - - -
    -

    The binary data which encodes the instance

    -
    -
    -
    -
    -

    Return Value

    -

    The decoded instance

    -
    -
    -
    -
  • -
  • -
    - - - - decode(_:from:) - -
    -
    -
    -
    -
    -
    -

    Decode a single value from binary data using a default decoder.

    -
    -

    Throws

    - Errors of type DecodingError or ProtobufDecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func decode<T>(_ type: T.Type = T.self, from data: Data) throws -> T where T : Decodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - type - - -
    -

    The type to decode.

    -
    -
    - - data - - -
    -

    The binary data which encodes the instance

    -
    -
    -
    -
    -

    Return Value

    -

    The decoded instance

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Classes/ProtobufEncoder.html b/docs/Classes/ProtobufEncoder.html deleted file mode 100644 index b3956f9..0000000 --- a/docs/Classes/ProtobufEncoder.html +++ /dev/null @@ -1,385 +0,0 @@ - - - - ProtobufEncoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

ProtobufEncoder

-
-
- -
public final class ProtobufEncoder
- -
-
-

An encoder to convert Codable objects to protobuf binary data.

- -

The encoder provides only limited compatibility with Google’s Protocol Buffers.

- -

Encoding unsupported data types causes ProtobufEncodingError errors.

- -

Construct an encoder when converting instances to binary data, and feed the message(s) into it:

-
let message: Message = ...
-
-let encoder = ProtobufEncoder()
-let data = try encoder.encode(message)
-
-
-

Note

- An ecoder can be used to encode multiple messages. - -
- -
-
-
-
    -
  • -
    - - - - userInfo - -
    -
    -
    -
    -
    -
    -

    Any contextual information set by the user for encoding.

    - -

    This dictionary is passed to all containers during the encoding process.

    - -

    Contains also keys for any custom options set for the encoder. -See sortKeysDuringEncoding.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var userInfo: [CodingUserInfoKey : Any]
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Create a new binary encoder.

    -
    -

    Note

    - An encoder can be used to encode multiple messages. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode a value to binary data.

    -
    -

    Throws

    - Errors of type EncodingError or ProtobufEncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode<T>(_ value: T) throws -> Data where T : Encodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - value - - -
    -

    The value to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The encoded data

    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode a single value to binary data using a default encoder.

    -
    -

    Throws

    - Errors of type EncodingError or ProtobufEncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func encode<T>(_ value: T) throws -> Data where T : Encodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - value - - -
    -

    The value to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The encoded data

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Enums.html b/docs/Enums.html deleted file mode 100644 index 1b4f72d..0000000 --- a/docs/Enums.html +++ /dev/null @@ -1,276 +0,0 @@ - - - - Enumerations Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Enumerations

-

The following enumerations are available globally.

- -
-
-
-
    -
  • -
    - - - - DataType - -
    -
    -
    -
    -
    -
    -

    The data type specifying how a value is encoded on the wire.

    - -

    The data type is mixed into the key for each value to indicate how many bytes the value occupies in the following bytes.

    - -

    The data type is equivalent to the Protocol Buffer Wire Type, but extended to support more data types.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum DataType : Int
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtobufDecodingError - -
    -
    -
    -
    -
    -
    -

    An error produced while decoding binary data.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum ProtobufDecodingError : Error
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtobufEncodingError - -
    -
    -
    -
    -
    -
    -

    An error thrown when encoding a value using ProtobufEncoder.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum ProtobufEncodingError : Error
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Enums/BinaryDecodingError.html b/docs/Enums/BinaryDecodingError.html deleted file mode 100644 index 39653e3..0000000 --- a/docs/Enums/BinaryDecodingError.html +++ /dev/null @@ -1,532 +0,0 @@ - - - - BinaryDecodingError Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryDecodingError

-
-
- -
public enum BinaryDecodingError : Error
- -
-
-

An error produced while decoding binary data.

- -
-
-
-
    -
  • -
    - - - - invalidDataSize - -
    -
    -
    -
    -
    -
    -

    The data for a primitive type did not have the right size.

    -
    -

    Note

    - This error is internal and should not occur in practice. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidDataSize
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - missingDataForKey(_:) - -
    -
    -
    -
    -
    -
    -

    The binary data is missing for a key.

    - -

    The associated value is the CodingKey not found in the data.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case missingDataForKey(CodingKey)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unknownDataType(_:) - -
    -
    -
    -
    -
    -
    -

    The binary data contained an unknown data type.

    - -

    The associated value is the unknown raw value of the data type.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unknownDataType(Int)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - prematureEndOfData - -
    -
    -
    -
    -
    -
    -

    The binary data ended before all values were decoded.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case prematureEndOfData
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - invalidString - -
    -
    -
    -
    -
    -
    -

    A String contained in the data could not be decoded.

    - -

    The failing string can be either a value, or a string key (e.g. a property name or enum case).

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidString
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    An integer encoded in the binary data as a varint does not fit into the specified integer type, producing an overflow.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case variableLengthEncodedIntegerOutOfRange
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - multipleValuesForKey - -
    -
    -
    -
    -
    -
    -

    The binary data contains multiple values for a key.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleValuesForKey
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - dataCorrupted(_:) - -
    -
    -
    -
    -
    -
    -

    An indication that the data is corrupted or otherwise invalid.

    - -

    As an associated value, this case contains the context for debugging.

    - -

    This error can occur when an unknown enum value was decoded.

    -
    -

    Note

    - This error case mirrors DecodingError.dataCorrupted() - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case dataCorrupted(DecodingError.Context)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - typeMismatch(_:_:) - -
    -
    -
    -
    -
    -
    -

    An indication that a value of the given type could not be decoded because it did not match the type of what was found in the encoded payload.

    - -

    As associated values, this case contains the attempted type and context for debugging.

    -
    -

    Note

    - This error case mirrors DecodingError.typeMismatch() - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case typeMismatch(Any.Type, DecodingError.Context)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - valueNotFound(_:_:) - -
    -
    -
    -
    -
    -
    -

    An indication that a non-optional value of the given type was expected, but a null value was found.

    -
    -

    Note

    - This error case mirrors DecodingError.valueNotFound() - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case valueNotFound(Any.Type, DecodingError.Context)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unknownError(_:) - -
    -
    -
    -
    -
    -
    -

    An unexpected and unknown error occured during decoding.

    - -

    As the associated value, this case contains the original error.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unknownError(Error)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Enums/BinaryEncodingError.html b/docs/Enums/BinaryEncodingError.html deleted file mode 100644 index 4003b3b..0000000 --- a/docs/Enums/BinaryEncodingError.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - BinaryEncodingError Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryEncodingError

-
-
- -
public enum BinaryEncodingError : Error
- -
-
-

An error thrown when encoding a value using BinaryEncoder.

- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    A string could not be encoded to UTF-8.

    - -

    The associated value of the error is the failed string.

    - -

    The string can either be a string key (e.g. a property or enum case name), or a String value.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case stringEncodingFailed(String)
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    A procedural error occuring during encoding.

    - -

    A custom implementation of func encode(to encoder: Encoder) throws tried to encode multiple values into a single value container:

    -
    func encode(to encoder: Encoder) throws {
    -   var container = encoder.singleValueContainer()
    -   container.encode(...)
    -   container.encode(...) // Invalid
    -}
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleValuesInSingleValueContainer
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - invalidValue(_:_:) - -
    -
    -
    -
    -
    -
    -

    An indication that an encoder or its containers could not encode the given value.

    - -

    As associated values, this case contains the attempted value and context for debugging.

    -
    -

    Note

    - This error case mirrors EncodingError.invalidValue() - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidValue(Any, EncodingError.Context)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unknownError(_:) - -
    -
    -
    -
    -
    -
    -

    An unexpected and unknown error occured during encoding.

    - -

    As the associated value, this case contains the original error.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unknownError(Error)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Enums/DataType.html b/docs/Enums/DataType.html deleted file mode 100644 index 9948cdf..0000000 --- a/docs/Enums/DataType.html +++ /dev/null @@ -1,391 +0,0 @@ - - - - DataType Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

DataType

-
-
- -
public enum DataType : Int
- -
-
-

The data type specifying how a value is encoded on the wire.

- -

The data type is mixed into the key for each value to indicate how many bytes the value occupies in the following bytes.

- -

The data type is equivalent to the Protocol Buffer Wire Type, but extended to support more data types.

- -
-
-
-
    -
  • -
    - - - - variableLengthInteger - -
    -
    -
    -
    -
    -
    -

    An integer value encoded as a Base128 Varint. -The length can be determined by reading the value, -where the first (MSB) indicates whether another byte follows. -A Varint consumes up to 9 bytes, where all bits from the last byte are used, -resulting in 8 * 7 + 8 = 64 usable bits.

    - -

    For Protobuf encoding, the Varint can use up to 10 bytes, since the MSB is used for all bytes, including byte 9.

    - -

    See also Protocol Buffers: Base 128 Varints.

    - -

    Used for: Int, Int32, Int64, UInt, UInt32, UInt64, Bool

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case variableLengthInteger = 0
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - byte - -
    -
    -
    -
    -
    -
    -

    The value is encoded as a single byte.

    -
    -

    Note

    - This data type is incompatible with the protocol buffer specification. - -
    - -

    Used for: UInt8, Int8

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case byte = 6
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - twoBytes - -
    -
    -
    -
    -
    -
    -

    The value is encoded as two bytes.

    -
    -

    Note

    - This data type is incompatible with the protocol buffer specification. - -
    - -

    Used for: Int16, UInt16

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case twoBytes = 7
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - variableLength - -
    -
    -
    -
    -
    -
    -

    The value is encoded using first a length (as a UInt64 var-int) followed by the bytes.

    - -

    Used by: String, Data, complex types

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case variableLength = 2
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fourBytes - -
    -
    -
    -
    -
    -
    -

    The value is encoded using four bytes.

    - -

    Used for: Float, FixedWidth<Int32>, FixedWidth<UInt32>

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case fourBytes = 5
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - eightBytes - -
    -
    -
    -
    -
    -
    -

    The value is encoded using eight bytes.

    - -

    Used by: Double, FixedWidth<Int64>, FixedWidth<Int>, FixedWidth<UInt64>, FixedWidth<UInt>

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case eightBytes = 1
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Enums/ProtobufDecodingError.html b/docs/Enums/ProtobufDecodingError.html deleted file mode 100644 index 5dac617..0000000 --- a/docs/Enums/ProtobufDecodingError.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - ProtobufDecodingError Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

ProtobufDecodingError

-
-
- -
public enum ProtobufDecodingError : Error
- -
-
-

An error produced while decoding binary data.

- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unexpectedDictionaryKey
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - superNotSupported - -
    -
    -
    -
    -
    -
    -

    Protocol buffers don’t support inheritance, so super can’t be encoded.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case superNotSupported
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unsupportedType(_:) - -
    -
    -
    -
    -
    -
    -

    The encoded type contains a basic type that is not supported.

    - -

    The associated value contains a textual description of the unsupported type.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unsupportedType(String)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - invalidAccess(_:) - -
    -
    -
    -
    -
    -
    -

    A decoding feature was accessed which is not supported for protobuf encoding.

    - -

    The associated value contains a textual description of the invalid access.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidAccess(String)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Enums/ProtobufEncodingError.html b/docs/Enums/ProtobufEncodingError.html deleted file mode 100644 index 86cca4c..0000000 --- a/docs/Enums/ProtobufEncodingError.html +++ /dev/null @@ -1,560 +0,0 @@ - - - - ProtobufEncodingError Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

ProtobufEncodingError

-
-
- -
public enum ProtobufEncodingError : Error
- -
-
-

An error thrown when encoding a value using ProtobufEncoder.

- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    A procedural error occuring during encoding.

    - -

    A custom implementation of func encode(to encoder: Encoder) throws tried to encode multiple values into a single value container:

    -
    func encode(to encoder: Encoder) throws {
    -   var container = encoder.singleValueContainer()
    -   container.encode(...)
    -   container.encode(...) // Invalid
    -}
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleValuesInSingleValueContainer
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case noValueInSingleValueContainer
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - nilValuesNotSupported - -
    -
    -
    -
    -
    -
    -

    The encoded type contains optional values, which are not supported in the protocol buffer format.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case nilValuesNotSupported
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unsupportedType(_:) - -
    -
    -
    -
    -
    -
    -

    The encoded type contains a basic type that is not supported.

    - -

    The associated value contains a textual description of the unsupported type.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unsupportedType(String)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - superNotSupported - -
    -
    -
    -
    -
    -
    -

    Protocol buffers don’t support inheritance, so super can’t be encoded.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case superNotSupported
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - missingIntegerKey(_:) - -
    -
    -
    -
    -
    -
    -

    The encoded type contains properties which don’t have an integer key.

    - -

    The associated value contains the string key which is missing an integer key.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case missingIntegerKey(String)
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    All values in unkeyed containers must have the same type.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleTypesInUnkeyedContainer
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Field numbers must be positive integers not greater than 536870911 (2^29-1, or 0x1FFFFFFF)

    - -

    The associated value is the integer key that is out of range.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case integerKeyOutOfRange(Int)
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Multiple calls to container<>(keyedBy:), unkeyedContainer(), or singleValueContainer() for an encoder.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleContainersAccessed
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - noContainersAccessed - -
    -
    -
    -
    -
    -
    -

    No calls to container<>(keyedBy:), unkeyedContainer(), or singleValueContainer() for an encoder.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case noContainersAccessed
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Protobuf requires an unkeyed container as the root node

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case rootIsNotKeyedContainer
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - invalidAccess(_:) - -
    -
    -
    -
    -
    -
    -

    An unavailable encoding feature was accessed.

    - -

    The associated value contains a textual description of the unsupported access.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidAccess(String)
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case protobufDefinitionUnavailable(String)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Extensions.html b/docs/Extensions.html deleted file mode 100644 index c04129e..0000000 --- a/docs/Extensions.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - Extensions Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Extensions

-

The following extensions are available globally.

- -
-
-
-
    -
  • -
    - - - - DecodingKey - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension DecodingKey: Equatable
    -
    extension DecodingKey: Hashable
    -
    extension DecodingKey: CustomStringConvertible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MixedCodingKeyWrapper - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension MixedCodingKeyWrapper: Equatable
    -
    extension MixedCodingKeyWrapper: Hashable
    -
    extension MixedCodingKeyWrapper: Comparable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtoKeyWrapper - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension ProtoKeyWrapper: Equatable
    -
    extension ProtoKeyWrapper: Hashable
    -
    extension ProtoKeyWrapper: Comparable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - IntKeyWrapper - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension IntKeyWrapper: Hashable
    -
    extension IntKeyWrapper: Equatable
    -
    extension IntKeyWrapper: Comparable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Bool - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Bool: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Data - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Data: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Float - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Float: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Int - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Int: FixedSizeCompatible
    -
    extension Int: SignedValueCompatible
    -
    extension Int: ZigZagCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Int32 - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Int32: ZigZagCodable
    -
    extension Int32: FixedSizeCompatible
    -
    extension Int32: SignedValueCompatible
    -
    extension Int32: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Int64 - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Int64: FixedSizeCompatible
    -
    extension Int64: SignedValueCompatible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - String - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension String: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - UInt - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension UInt: FixedSizeCompatible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - UInt32 - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension UInt32: FixedSizeCompatible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - UInt64 - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension UInt64: FixedSizeCompatible
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Extensions/Int.html b/docs/Extensions/Int.html deleted file mode 100644 index 686c938..0000000 --- a/docs/Extensions/Int.html +++ /dev/null @@ -1,326 +0,0 @@ - - - - Int Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Int

-
-
- -
extension Int: FixedSizeCompatible
-
extension Int: SignedValueCompatible
-
extension Int: ZigZagCodable
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - positiveProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var positiveProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Extensions/Int32.html b/docs/Extensions/Int32.html deleted file mode 100644 index 44b5320..0000000 --- a/docs/Extensions/Int32.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - Int32 Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Int32

-
-
- -
extension Int32: ZigZagCodable
-
extension Int32: FixedSizeCompatible
-
extension Int32: SignedValueCompatible
-
extension Int32: ProtobufCodable
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - positiveProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var positiveProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Extensions/Int64.html b/docs/Extensions/Int64.html deleted file mode 100644 index dedfd41..0000000 --- a/docs/Extensions/Int64.html +++ /dev/null @@ -1,325 +0,0 @@ - - - - Int64 Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Int64

-
-
- -
extension Int64: FixedSizeCompatible
-
extension Int64: SignedValueCompatible
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - positiveProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var positiveProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Extensions/UInt.html b/docs/Extensions/UInt.html deleted file mode 100644 index 5df0857..0000000 --- a/docs/Extensions/UInt.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - UInt Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

UInt

-
-
- -
extension UInt: FixedSizeCompatible
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Extensions/UInt32.html b/docs/Extensions/UInt32.html deleted file mode 100644 index eceb923..0000000 --- a/docs/Extensions/UInt32.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - UInt32 Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

UInt32

-
-
- -
extension UInt32: FixedSizeCompatible
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Extensions/UInt64.html b/docs/Extensions/UInt64.html deleted file mode 100644 index 26bf37a..0000000 --- a/docs/Extensions/UInt64.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - UInt64 Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

UInt64

-
-
- -
extension UInt64: FixedSizeCompatible
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Guides.html b/docs/Guides.html deleted file mode 100644 index 767b429..0000000 --- a/docs/Guides.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - Guides Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- - -
- - diff --git a/docs/Protocols.html b/docs/Protocols.html deleted file mode 100644 index cd8b8b4..0000000 --- a/docs/Protocols.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - Protocols Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Protocols

-

The following protocols are available globally.

- -
-
-
-
    -
  • -
    - - - - ProtobufOneOf - -
    -
    -
    -
    -
    -
    -

    Add conformance to this protocol to enums which should be encoded as Protobuf Oneof values.

    - -

    Conform a suitable enum to the ProtobufOneOf protocol to indicate that it should be encoded as single value.

    - -

    The enum must have exactly one associated value for each case, and the integer keys must not overlap with the ones defined for the enclosing struct.

    -
    struct Test: Codable {
    -
    -   // The oneof field
    -   let alternatives: OneOf
    -
    -   // The OneOf definition
    -   enum OneOf: Codable, ProtobufOneOf {
    -       case integer(Int)
    -       case string(String)
    -
    -       // Field values, must not overlap with `Test.CodingKeys`
    -       enum CodingKeys: Int, CodingKey {
    -           case integer = 1
    -           case string = 2
    -       }
    -   }
    -
    -    enum CodingKeys: Int, CodingKey {
    -       // The field id of the Oneof field is not used
    -        case alternatives = 123456
    -    }
    -}
    -
    - -

    See: Protocol Buffer Language Guide: Oneof and Swift Protobuf: Oneof Fields

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public protocol ProtobufOneOf
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - FixedSizeCompatible - -
    -
    -
    -
    -
    -
    -

    An integer type which can be forced to use a fixed-length encoding instead of variable-length encoding.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public protocol FixedSizeCompatible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - SignedValueCompatible - -
    -
    -
    -
    -
    -
    -

    A signed integer which can be forced to use zig-zag encoding.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public protocol SignedValueCompatible
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Protocols/FixedSizeCompatible.html b/docs/Protocols/FixedSizeCompatible.html deleted file mode 100644 index c3204f1..0000000 --- a/docs/Protocols/FixedSizeCompatible.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - FixedSizeCompatible Protocol Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

FixedSizeCompatible

-
-
- -
public protocol FixedSizeCompatible
- -
-
-

An integer type which can be forced to use a fixed-length encoding instead of variable-length encoding.

- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    -

    The wire type of the type, which has a constant length

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    -

    The protobuf type equivalent to the fixed size type

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    -

    The value encoded as fixed size binary data

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Decode the value from binary data.

    -
    -

    Throws

    - DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - data - - -
    -

    The binary data of the correct size for the type.

    -
    -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Protocols/SignedValueCompatible.html b/docs/Protocols/SignedValueCompatible.html deleted file mode 100644 index dac9f05..0000000 --- a/docs/Protocols/SignedValueCompatible.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - SignedValueCompatible Protocol Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

SignedValueCompatible

-
-
- -
public protocol SignedValueCompatible
- -
-
-

A signed integer which can be forced to use zig-zag encoding.

- -
-
-
-
    -
  • -
    - - - - positiveProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    var positiveProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Structs.html b/docs/Structs.html deleted file mode 100644 index 9b9d7dc..0000000 --- a/docs/Structs.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - Structures Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Structures

-

The following structures are available globally.

- -
-
-
-
    -
  • -
    - - - - FixedSize - -
    -
    -
    -
    -
    -
    -

    A wrapper for integer values which ensures that values are encoded in binary format using a fixed size.

    - -

    Use the property wrapped within a Codable definition to enforce fixed-width encoding for a property:

    -
     struct MyStruct: Codable {
    -
    -     /// Always encoded as 4 bytes
    -     @FixedSize
    -     var largeInteger: Int32
    - }
    -
    - -

    The FixedSize property wrapper is supported for UInt32, UInt64, Int32, and Int64 types.

    - - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    @propertyWrapper
    -public struct FixedSize<WrappedValue>: ExpressibleByIntegerLiteral
    -where WrappedValue: FixedSizeCompatible,
    -      WrappedValue: FixedWidthInteger,
    -      WrappedValue: Codable
    -
    extension FixedSize: Equatable
    -
    extension FixedSize: Comparable
    -
    extension FixedSize: Hashable
    -
    extension FixedSize: Encodable
    -
    extension FixedSize: Decodable
    -
    extension FixedSize: CodablePrimitive, DataTypeProvider where WrappedValue: DataTypeProvider
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - SignedValue - -
    -
    -
    -
    -
    -
    -

    A wrapper for integers more efficient for negative values.

    - -

    This encoding format enforces Zig-Zag encoding, which is more efficient when numbers are often negative.

    -
    -

    Note

    - This wrapper is only useful when encoding and decoding to/from protobuf data. -It has no effect for the standard BinaryEncoder and BinaryDecoder, where integer values are -encoded using Zig-Zag encoding by default. - -
    - -

    Use the property wrapped within a Codable definition to enforce fixed-width encoding for a property:

    -
     struct MyStruct: Codable {
    -
    -     /// Efficient for small positive and negative values
    -     @SignedValue
    -     var count: Int32
    - }
    -
    - -

    The SignedValue property wrapper is supported for Int, Int32, and Int64 types.

    - - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    @propertyWrapper
    -public struct SignedValue<WrappedValue>: ExpressibleByIntegerLiteral
    -where WrappedValue: SignedValueCompatible,
    -      WrappedValue: SignedInteger,
    -      WrappedValue: FixedWidthInteger,
    -      WrappedValue: Codable
    -
    extension SignedValue: Equatable
    -
    extension SignedValue: Comparable
    -
    extension SignedValue: Hashable
    -
    extension SignedValue: Encodable
    -
    extension SignedValue: Decodable
    -
    extension SignedValue: CodablePrimitive, DataTypeProvider where WrappedValue: ZigZagCodable, WrappedValue: DataTypeProvider
    -
    extension SignedValue: ProtobufCodable where WrappedValue: ZigZagCodable, WrappedValue: SignedValueCompatible
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Structs/FixedSize.html b/docs/Structs/FixedSize.html deleted file mode 100644 index 0fe506e..0000000 --- a/docs/Structs/FixedSize.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - FixedSize Structure Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

FixedSize

-
-
- -
@propertyWrapper
-public struct FixedSize<WrappedValue>: ExpressibleByIntegerLiteral
-where WrappedValue: FixedSizeCompatible,
-      WrappedValue: FixedWidthInteger,
-      WrappedValue: Codable
-
extension FixedSize: Equatable
-
extension FixedSize: Comparable
-
extension FixedSize: Hashable
-
extension FixedSize: Encodable
-
extension FixedSize: Decodable
-
extension FixedSize: CodablePrimitive, DataTypeProvider where WrappedValue: DataTypeProvider
- -
-
-

A wrapper for integer values which ensures that values are encoded in binary format using a fixed size.

- -

Use the property wrapped within a Codable definition to enforce fixed-width encoding for a property:

-
 struct MyStruct: Codable {
-
-     /// Always encoded as 4 bytes
-     @FixedSize
-     var largeInteger: Int32
- }
-
- -

The FixedSize property wrapper is supported for UInt32, UInt64, Int32, and Int64 types.

- - -
-
-
-
    -
  • -
    - - - - IntegerLiteralType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public typealias IntegerLiteralType = WrappedValue.IntegerLiteralType
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - wrappedValue - -
    -
    -
    -
    -
    -
    -

    The value wrapped in the fixed-size container

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var wrappedValue: WrappedValue
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(wrappedValue:) - -
    -
    -
    -
    -
    -
    -

    Wrap an integer value in a fixed-size container

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(wrappedValue: WrappedValue)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - wrappedValue - - -
    -

    The integer to wrap

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(integerLiteral:) - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(integerLiteral value: WrappedValue.IntegerLiteralType)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - <(_:_:) - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func < (lhs: FixedSize<WrappedValue>, rhs: FixedSize<WrappedValue>) -> Bool
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(to:) - -
    -
    -
    -
    -
    -
    -

    Encode the wrapped value transparently to the given encoder.

    -
    -

    Throws

    - Errors from the decoder when attempting to encode a value in a single value container. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode(to encoder: Encoder) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - encoder - - -
    -

    The encoder to use for encoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(from:) - -
    -
    -
    -
    -
    -
    -

    Decode a wrapped value from a decoder.

    -
    -

    Throws

    - Errors from the decoder when reading a single value container or decoding the wrapped value from it. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(from decoder: Decoder) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - decoder - - -
    -

    The decoder to use for decoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - max - -
    -
    -
    -
    -
    -
    -

    The maximum representable integer in this type.

    - -

    For unsigned integer types, this value is (2 ** bitWidth) - 1, where -** is exponentiation. For signed integer types, this value is -(2 ** (bitWidth - 1)) - 1.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var max: `Self` { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - min - -
    -
    -
    -
    -
    -
    -

    The minimum representable integer in this type.

    - -

    For unsigned integer types, this value is always 0. For signed integer -types, this value is -(2 ** (bitWidth - 1)), where ** is -exponentiation.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var min: `Self` { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - zero - -
    -
    -
    -
    -
    -
    -

    The zero value.

    - -

    Zero is the identity element for addition. For any value, x + .zero == x and .zero + x == x.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var zero: `Self` { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/Structs/SignedValue.html b/docs/Structs/SignedValue.html deleted file mode 100644 index 531d295..0000000 --- a/docs/Structs/SignedValue.html +++ /dev/null @@ -1,576 +0,0 @@ - - - - SignedValue Structure Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

SignedValue

-
-
- -
@propertyWrapper
-public struct SignedValue<WrappedValue>: ExpressibleByIntegerLiteral
-where WrappedValue: SignedValueCompatible,
-      WrappedValue: SignedInteger,
-      WrappedValue: FixedWidthInteger,
-      WrappedValue: Codable
-
extension SignedValue: Equatable
-
extension SignedValue: Comparable
-
extension SignedValue: Hashable
-
extension SignedValue: Encodable
-
extension SignedValue: Decodable
-
extension SignedValue: CodablePrimitive, DataTypeProvider where WrappedValue: ZigZagCodable, WrappedValue: DataTypeProvider
-
extension SignedValue: ProtobufCodable where WrappedValue: ZigZagCodable, WrappedValue: SignedValueCompatible
- -
-
-

A wrapper for integers more efficient for negative values.

- -

This encoding format enforces Zig-Zag encoding, which is more efficient when numbers are often negative.

-
-

Note

- This wrapper is only useful when encoding and decoding to/from protobuf data. -It has no effect for the standard BinaryEncoder and BinaryDecoder, where integer values are -encoded using Zig-Zag encoding by default. - -
- -

Use the property wrapped within a Codable definition to enforce fixed-width encoding for a property:

-
 struct MyStruct: Codable {
-
-     /// Efficient for small positive and negative values
-     @SignedValue
-     var count: Int32
- }
-
- -

The SignedValue property wrapper is supported for Int, Int32, and Int64 types.

- - -
-
-
-
    -
  • -
    - - - - IntegerLiteralType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public typealias IntegerLiteralType = WrappedValue.IntegerLiteralType
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - wrappedValue - -
    -
    -
    -
    -
    -
    -

    The value wrapped in the fixed-size container

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var wrappedValue: WrappedValue
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(wrappedValue:) - -
    -
    -
    -
    -
    -
    -

    Wrap an integer value in a fixed-size container

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(wrappedValue: WrappedValue)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - wrappedValue - - -
    -

    The integer to wrap

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(integerLiteral:) - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(integerLiteral value: WrappedValue.IntegerLiteralType)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - <(_:_:) - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func < (lhs: SignedValue<WrappedValue>, rhs: SignedValue<WrappedValue>) -> Bool
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(to:) - -
    -
    -
    -
    -
    -
    -

    Encode the wrapped value transparently to the given encoder.

    -
    -

    Throws

    - Errors from the decoder when attempting to encode a value in a single value container. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode(to encoder: Encoder) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - encoder - - -
    -

    The encoder to use for encoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(from:) - -
    -
    -
    -
    -
    -
    -

    Decode a wrapped value from a decoder.

    -
    -

    Throws

    - Errors from the decoder when reading a single value container or decoding the wrapped value from it. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(from decoder: Decoder) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - decoder - - -
    -

    The decoder to use for decoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - zero - -
    -
    -
    -
    -
    -
    -

    The zero value.

    - -

    Zero is the identity element for addition. For any value, x + .zero == x and .zero + x == x.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var zero: `Self` { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - max - -
    -
    -
    -
    -
    -
    -

    The maximum representable integer in this type.

    - -

    For unsigned integer types, this value is (2 ** bitWidth) - 1, where -** is exponentiation. For signed integer types, this value is -(2 ** (bitWidth - 1)) - 1.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var max: `Self` { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - min - -
    -
    -
    -
    -
    -
    -

    The minimum representable integer in this type.

    - -

    For unsigned integer types, this value is always 0. For signed integer -types, this value is -(2 ** (bitWidth - 1)), where ** is -exponentiation.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var min: `Self` { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/assets/logo.png b/docs/assets/logo.png deleted file mode 100644 index 9c6f100..0000000 Binary files a/docs/assets/logo.png and /dev/null differ diff --git a/docs/assets/platforms.svg b/docs/assets/platforms.svg deleted file mode 100644 index 405ea78..0000000 --- a/docs/assets/platforms.svg +++ /dev/null @@ -1 +0,0 @@ -platformsplatformsiOS | macOS | Linux | tvOS | watchOSiOS | macOS | Linux | tvOS | watchOS \ No newline at end of file diff --git a/docs/assets/swift.svg b/docs/assets/swift.svg deleted file mode 100644 index c60e0cb..0000000 --- a/docs/assets/swift.svg +++ /dev/null @@ -1 +0,0 @@ -SwiftSwift5.65.6 \ No newline at end of file diff --git a/docs/badge.svg b/docs/badge.svg deleted file mode 100644 index a096fec..0000000 --- a/docs/badge.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - documentation - - - documentation - - - 100% - - - 100% - - - diff --git a/docs/binaryformat.html b/docs/binaryformat.html deleted file mode 100644 index e71697f..0000000 --- a/docs/binaryformat.html +++ /dev/null @@ -1,505 +0,0 @@ - - - - BinaryFormat Reference - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
- -

Binary data structure

- -

Note: The BinaryCodable format is optimized for size, but does not go all-out to create the smallest binary sizes possible. -The binary format, while being efficient, needs to serve as a general-purpose encoding, which will never be as efficient than a custom format optimized for a very specific use case. -If this is your goal, then simply using Codable with it’s key-value approach will not be the best solution. An unkeyed format optimized for the actually encoded data will be more suitable. But if you’re really looking into this kind of efficiency, then you probably know this already.

- -

The encoding format used by BinaryCodable is similar to Google’s Protocol Buffers in some aspects, but provides much more flexibility regarding the different types which can be encoded, including the ability to encode Optional, Set, single values, multidimensional Arrays, and more.

-

Integer encoding

- -

Integers are encoded with different strategies depending on their size. Smaller types, like UInt8, Int8, UInt16, and Int16 are encoded using their binary representations in little-endian format.

- -

Larger integers, like UInt32, Int32, UInt64, Int64, Int, and UInt are (by default) encoded using variable length zig-zag encoding, similar to Protobuf signed integers. This means that smaller values are encoded as shorter binary representations, which is useful if integer values are mostly small. -Note: The Varint implementation is not equivalent to Protobuf, since BinaryCodable uses the last byte of a large integer directly, and thus encodes Int.max with 9 Byte instead of 10. This encoding is adapted when enforcing protobuf compatibility.

- -

The property wrapper FixedSize forces the values to be encoded using their little-endian representations.

-

Floating-point types

- -

Float and Double values are encoded using their binary representations in little-endian format.

-

Strings

- -

Swift String values are encoded using their UTF-8 representations. If a string can’t be encoded this way, then encoding fails.

-

Booleans

- -

Bool values are always encoded as a single byte, using 1 for true, and 0 for false.

-

Arrays

- -

Arrays (and other sequences) are encoded by converting each item to binary data, and concatenating the results. Elements with variable length (like String) are prepended with their length encoded as a Varint.

-

Arrays of Optionals

- -

It is possible to encode arrays where the elements are Optional, e.g. [Bool?]. -For all types with compiler-generated Codable conformance, an optional is prepended with one byte (1 for the .some() case, 0 for nil). -If the optional has a value, then the encoded data follows the 1 byte. -If nil is encoded, then no additional data is added. -This means that an array of [Bool?] with the values [true, nil, false] is encoded as [1, 1, 0, 1, 0].

-
-

Note

- One benefit of this encoding is that top-level sequences can be joined using their binary data, where encoded([a,b]) | encoded([c,d]) == encoded([a,b,c,d]). - -
- -

Custom implementations of Encodable and Decodable can directly call encodeNil() on UnkeyedEncodingContainer and decodeNil() on UnkeyedDecodingContainer. -This feature is not supported in the standard configuration and will result in a fatal error. -If these functions are needed, then the prependNilIndexSetForUnkeyedContainers must be set for the encoder and decoder. -If this option is set to true, then each unkeyed container is prepended with a “nil index set”. -It first consists of the number of nil elements in the sequence (only those encoded using encodeNil()), encoded as a Varint. -Then follow the indices in the array where nil values are present, each encoded as a Varint. -The decoder can then first parse this nil set, and return the appropriate value for each position where a nil value is encoded when decodeNil() is called. -This approach is fairly efficient while only few nil values are encoded, or while the sequence doesn’t contain a large number of elements. -For arrays that don’t contain optionals, only a single byte (0) is prepended to the binary representation, to signal that there are no nil indices in the sequence.

- -

More efficient ways could be devised to handle arrays of optionals, like specifying the number of nil or non-nil elements following one another, but the increased encoding and decoding complexity don’t justify these gains in communication efficiency.

-

Structs

- -

Structs are encoded using Codable‘s KeyedEncodingContainer, which uses String or Int coding keys to distinguish the properties of the types. -By default, Swift uses the property names as String keys, which are used to encode each property as a key-value pair on the wire. -The first value is a Varint, which contains the length of the string key, plus additional information about the data associated with the key. -The bits 0-2 are used to signal the size value, and bit 3 of the Varint indicates whether the key is a string key (1 = string, 0 = int). -The following data types are possible:

- -

| Data type | Raw value | Protobuf | Swift types | Description | -| :———————- | :——– | :—————- | :————————————————————————————- | -| variableLengthInteger | 0 | varint/zigzag | Int, Int32, Int64, UInt, UInt32, UInt64 | A Base128 Varint using 1-9 bytes of data | -| eightBytes | 1 | fixed64bit | Double, FixedSize<Int64>, FixedSize<Int>, FixedSize<UInt64>, FixedSize<UInt> | A 64-bit float or integer in little-endian encoding. | -| variableLength | 2 | delimited | String, Struct, … | The length of the data encoded as a Varint followed by length bytes | -| fourBytes | 5 | fixed32bit | Float, FixedSize<Int32>, FixedSize<UInt32> | A 32-bit float or integer in little-endian encoding. | -| byte | 6 | - | Bool, UInt8, Int8 | A single byte storing a number or boolean | -| twoBytes | 7 | - | Int16, UInt16 | Two bytes storing an integer using little-endian format |

- -

With the four lower bits occupied by the data type and the string key indicator, the remaining bits are left to encode the length of the string key.

- -

For example, a property named xyz of type UInt8 with value 123 would be encoded to the following:

- - - - - - - - - - - - - - - - - - - - - - - - -
Byte 0Byte 1Byte 2Byte 3Byte 4
0 011 1 11001111000011110010111101001111011
Length 3, String key, Data type bytexyz123
-

Integer keys

- -

The Swift Codable framework also provides the ability to specify integer keys for properties, which can significantly reduce the binary size. Integer keys can be assigned to properties by implementing custom CodingKeys for a type:

-
struct MyCodable: Codable {
-
-    let xyz: UInt8
-
-    enum CodingKeys: Int, CodingKey {
-        case xyz = 2
-    }
-}
-
- -

Integer coding keys are encoded as Varint instead of the String key length. This results in the following encoding for the same example as before:

- - - - - - - - - - - - - - - -
Byte 0Byte 1
0 010 0 11001111011
Integer key 2, Int key, Data type byte123
- -

Evidently this is a significant improvement, especially for long property names. Note that while it is possible to specify any integer as the key (between 2^59 and -2^59), small, positive integers are the most efficient.

-

Optional properties

- -

Any properties of structs or other keyed containers is omitted from the binary data, i.e. nothing is encoded. -The absence of a key then indicates to the decoder that the value is nil. -For multiple optionals (e.g. Bool??), the inner optional is encoded as a varLen type, with the same encoding as optionals in arrays.

-

Codable quirks

- -

There are some interesting details on how Codable treats certain types, which produce unexpected binary data. Although BinaryCodable decodes all these cases correctly, implementing these “features” may be difficult on other platforms.

-

Enums with associated values

- -

Given an enum with associated values:

-
enum MyEnum: Codable {
-    case one(String)
-    case two(Bool, Data)
-}
-
- -

The encoding will consist of:

- -
    -
  • The enum case name as a String key
  • -
  • A keyed container with: - -
      -
    • All associated values in the order of their definition, keyed by "_0", "_1", …
    • -
  • -
- -

For example, the value

-
let value = MyEnum.one("Some")
-
- -

would be encoded as:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Byte 0Byte 1 - 3Byte 4Byte 5Byte 6 - 7Byte 8Byte 9 - 12
0x3A0x6F 0x6E 0x650x080x2A0x5F 0x300x040x53 0x6E 0x64 0x08
String key (Len 3), VarLenoneLength 8String key (Len 2)_0Length 4Some
- -

Let’s use the same example with integer keys:

-
enum MyEnum: Codable {
-    case one(String)
-    case two(Bool, UInt8)
-
-    enum CodingKeys: Int, CodingKey {
-        case one = 1
-        case two = 2
-    }
-}
-
- -

Then, the value

-
let value = MyEnum.two(true, 123)
-
- -

would be encoded as:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Byte 0Byte 1Byte 2Byte 3 - 4Byte 5Byte 6Byte 7 - 8Byte 9
0x220x080x2E0x5F 0x300x010x2E0x5F 0x310x7B
Int key (2), VarLenLength 8String key (Len 2), Byte_0Bool(true)String key (Len 2), Byte_1UInt8(123)
- -

Note: Since the associated values are encoded in a keyed container, there order in the binary data may be different, unless the sortKeysDuringEncoding option is set to true.

-

Special dictionaries

- -

Most dictionaries are treated as Unkeyed Containers by Codable, and each key value pair is encoded by first encoding the key, followed by the value, thus creating the flat structure:

- - - - - - - - - - - -
Key 1Value 1Key 2Value 2Key 3Value 3
-

Dictionaries with Integer keys

- -

For all dictionaries, which use Int as the key, e.g. [Int: String], [Int: Int], or generally [Int: ...], the encoding is done using a Keyed container, where each dictionary value is encoded using a CodingKey with an integer value. This results in a structure more resembling struct encoding with integer keys:

- - - - - - - - - - - - - - - - - - - -
Byte(s)Byte(s)Byte(s)Byte(s)Byte(s)Byte(s)
Int Key(Key 1), Data typeValue 1Int Key(Key 2), Data typeValue 2Int Key(Key 3), Data typeValue 3
- -

For example, the following works:

-
struct MyStruct: Codable {
-    let a: Int
-    let b: Int
-    let c: Int
-}
-
-// Encode a dictionary
-let input: [String: Int] = ["a" : 123, "b": 0, "c": -123456]
-let data = try BinaryEncoder.encode(input)
-
-// Decode as struct
-let decoded = try BinaryDecoder.decode(MyStruct.self, from: data)
-
- -

It also works the other way round:

-
// Encode struct
-let input = MyStruct(a: 123, b: 0, c: -123456)
-let data = try BinaryEncoder.encode(input)
-
-// Decode as dictionary
-let decoded: [String: Int] = try BinaryDecoder.decode(from: data)
-
- -

Note that this only works for dictionaries with concrete Encodable values, e.g. [String: Encodable] won’t work.

-

Dictionaries with String keys

- -

For dictionaries with String keys ([String: ...]), the process is similar to the above, except with CodingKeys having the stringValue of the key. There is another weird exception though: Whenever a String can be represented by an integer (i.e. when String(key) != nil), then the corresponding CodingKey will have its integerValue also set. This means that for dictionaries with integer keys, there may be a mixture of integer and string keys present in the binary data, depending on the input values. But don’t worry, BinaryCodable will also handle these cases correctly.

-

Stream encoding

- -

The encoding for data streams is only differs from standard encoding in two key aspects.

-

Added length information

- -

Each top-level element is encoded as if it is part of an unkeyed container (which it essentially is), meaning that each element has the necessary length information prepended to determine it’s size. -Only types with data type variable length have their length prepended using varint encoding. -This concerns String and Data, as well as complex types like structs and arrays, among others.

-

Optionals

- -

A single byte is prepended to each Optional element, where binary 0x01 is used to indicate a non-optional value, and 0x00 is used to signal an optional value. -nil values have no additional data, so each is encoded using one byte.

- -
-
- -
-
- - diff --git a/docs/css/highlight.css b/docs/css/highlight.css deleted file mode 100644 index c170357..0000000 --- a/docs/css/highlight.css +++ /dev/null @@ -1,202 +0,0 @@ -/*! Jazzy - https://github.com/realm/jazzy - * Copyright Realm Inc. - * SPDX-License-Identifier: MIT - */ -/* Credit to https://gist.github.com/wataru420/2048287 */ -.highlight .c { - color: #999988; - font-style: italic; } - -.highlight .err { - color: #a61717; - background-color: #e3d2d2; } - -.highlight .k { - color: #000000; - font-weight: bold; } - -.highlight .o { - color: #000000; - font-weight: bold; } - -.highlight .cm { - color: #999988; - font-style: italic; } - -.highlight .cp { - color: #999999; - font-weight: bold; } - -.highlight .c1 { - color: #999988; - font-style: italic; } - -.highlight .cs { - color: #999999; - font-weight: bold; - font-style: italic; } - -.highlight .gd { - color: #000000; - background-color: #ffdddd; } - -.highlight .gd .x { - color: #000000; - background-color: #ffaaaa; } - -.highlight .ge { - color: #000000; - font-style: italic; } - -.highlight .gr { - color: #aa0000; } - -.highlight .gh { - color: #999999; } - -.highlight .gi { - color: #000000; - background-color: #ddffdd; } - -.highlight .gi .x { - color: #000000; - background-color: #aaffaa; } - -.highlight .go { - color: #888888; } - -.highlight .gp { - color: #555555; } - -.highlight .gs { - font-weight: bold; } - -.highlight .gu { - color: #aaaaaa; } - -.highlight .gt { - color: #aa0000; } - -.highlight .kc { - color: #000000; - font-weight: bold; } - -.highlight .kd { - color: #000000; - font-weight: bold; } - -.highlight .kp { - color: #000000; - font-weight: bold; } - -.highlight .kr { - color: #000000; - font-weight: bold; } - -.highlight .kt { - color: #445588; } - -.highlight .m { - color: #009999; } - -.highlight .s { - color: #d14; } - -.highlight .na { - color: #008080; } - -.highlight .nb { - color: #0086B3; } - -.highlight .nc { - color: #445588; - font-weight: bold; } - -.highlight .no { - color: #008080; } - -.highlight .ni { - color: #800080; } - -.highlight .ne { - color: #990000; - font-weight: bold; } - -.highlight .nf { - color: #990000; } - -.highlight .nn { - color: #555555; } - -.highlight .nt { - color: #000080; } - -.highlight .nv { - color: #008080; } - -.highlight .ow { - color: #000000; - font-weight: bold; } - -.highlight .w { - color: #bbbbbb; } - -.highlight .mf { - color: #009999; } - -.highlight .mh { - color: #009999; } - -.highlight .mi { - color: #009999; } - -.highlight .mo { - color: #009999; } - -.highlight .sb { - color: #d14; } - -.highlight .sc { - color: #d14; } - -.highlight .sd { - color: #d14; } - -.highlight .s2 { - color: #d14; } - -.highlight .se { - color: #d14; } - -.highlight .sh { - color: #d14; } - -.highlight .si { - color: #d14; } - -.highlight .sx { - color: #d14; } - -.highlight .sr { - color: #009926; } - -.highlight .s1 { - color: #d14; } - -.highlight .ss { - color: #990073; } - -.highlight .bp { - color: #999999; } - -.highlight .vc { - color: #008080; } - -.highlight .vg { - color: #008080; } - -.highlight .vi { - color: #008080; } - -.highlight .il { - color: #009999; } diff --git a/docs/css/jazzy.css b/docs/css/jazzy.css deleted file mode 100644 index 2e38713..0000000 --- a/docs/css/jazzy.css +++ /dev/null @@ -1,439 +0,0 @@ -/*! Jazzy - https://github.com/realm/jazzy - * Copyright Realm Inc. - * SPDX-License-Identifier: MIT - */ -html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { - background: transparent; - border: 0; - margin: 0; - outline: 0; - padding: 0; - vertical-align: baseline; } - -body { - background-color: #f2f2f2; - font-family: Helvetica, freesans, Arial, sans-serif; - font-size: 14px; - -webkit-font-smoothing: subpixel-antialiased; - word-wrap: break-word; } - -h1, h2, h3 { - margin-top: 0.8em; - margin-bottom: 0.3em; - font-weight: 100; - color: black; } - -h1 { - font-size: 2.5em; } - -h2 { - font-size: 2em; - border-bottom: 1px solid #e2e2e2; } - -h4 { - font-size: 13px; - line-height: 1.5; - margin-top: 21px; } - -h5 { - font-size: 1.1em; } - -h6 { - font-size: 1.1em; - color: #777; } - -.section-name { - color: gray; - display: block; - font-family: Helvetica; - font-size: 22px; - font-weight: 100; - margin-bottom: 15px; } - -pre, code { - font: 0.95em Menlo, monospace; - color: #777; - word-wrap: normal; } - -p code, li code { - background-color: #eee; - padding: 2px 4px; - border-radius: 4px; } - -pre > code { - padding: 0; } - -a { - color: #0088cc; - text-decoration: none; } - a code { - color: inherit; } - -ul { - padding-left: 15px; } - -li { - line-height: 1.8em; } - -img { - max-width: 100%; } - -blockquote { - margin-left: 0; - padding: 0 10px; - border-left: 4px solid #ccc; } - -hr { - height: 1px; - border: none; - background-color: #e2e2e2; } - -.footnote-ref { - display: inline-block; - scroll-margin-top: 70px; } - -.footnote-def { - scroll-margin-top: 70px; } - -.content-wrapper { - margin: 0 auto; - width: 980px; } - -header { - font-size: 0.85em; - line-height: 32px; - background-color: #414141; - position: fixed; - width: 100%; - z-index: 3; } - header img { - padding-right: 6px; - vertical-align: -3px; - height: 16px; } - header a { - color: #fff; } - header p { - float: left; - color: #999; } - header .header-right { - float: right; - margin-left: 16px; } - -#breadcrumbs { - background-color: #f2f2f2; - height: 21px; - padding-top: 17px; - position: fixed; - width: 100%; - z-index: 2; - margin-top: 32px; } - #breadcrumbs #carat { - height: 10px; - margin: 0 5px; } - -.sidebar { - background-color: #f9f9f9; - border: 1px solid #e2e2e2; - overflow-y: auto; - overflow-x: hidden; - position: fixed; - top: 70px; - bottom: 0; - width: 230px; - word-wrap: normal; } - -.nav-groups { - list-style-type: none; - background: #fff; - padding-left: 0; } - -.nav-group-name { - border-bottom: 1px solid #e2e2e2; - font-size: 1.1em; - font-weight: 100; - padding: 15px 0 15px 20px; } - .nav-group-name > a { - color: #333; } - -.nav-group-tasks { - margin-top: 5px; } - -.nav-group-task { - font-size: 0.9em; - list-style-type: none; - white-space: nowrap; } - .nav-group-task a { - color: #888; } - -.main-content { - background-color: #fff; - border: 1px solid #e2e2e2; - margin-left: 246px; - position: absolute; - overflow: hidden; - padding-bottom: 20px; - top: 70px; - width: 734px; } - .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { - margin-bottom: 1em; } - .main-content p { - line-height: 1.8em; } - .main-content section .section:first-child { - margin-top: 0; - padding-top: 0; } - .main-content section .task-group-section .task-group:first-of-type { - padding-top: 10px; } - .main-content section .task-group-section .task-group:first-of-type .section-name { - padding-top: 15px; } - .main-content section .heading:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - .main-content .section-name p { - margin-bottom: inherit; - line-height: inherit; } - .main-content .section-name code { - background-color: inherit; - padding: inherit; - color: inherit; } - -.section { - padding: 0 25px; } - -.highlight { - background-color: #eee; - padding: 10px 12px; - border: 1px solid #e2e2e2; - border-radius: 4px; - overflow-x: auto; } - -.declaration .highlight { - overflow-x: initial; - padding: 0 40px 40px 0; - margin-bottom: -25px; - background-color: transparent; - border: none; } - -.section-name { - margin: 0; - margin-left: 18px; } - -.task-group-section { - margin-top: 10px; - padding-left: 6px; - border-top: 1px solid #e2e2e2; } - -.task-group { - padding-top: 0px; } - -.task-name-container a[name]:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - -.section-name-container { - position: relative; - display: inline-block; } - .section-name-container .section-name-link { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - margin-bottom: 0; } - .section-name-container .section-name { - position: relative; - pointer-events: none; - z-index: 1; } - .section-name-container .section-name a { - pointer-events: auto; } - -.item { - padding-top: 8px; - width: 100%; - list-style-type: none; } - .item a[name]:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - .item code { - background-color: transparent; - padding: 0; } - .item .token, .item .direct-link { - display: inline-block; - text-indent: -20px; - padding-left: 3px; - margin-left: 35px; - font-size: 11.9px; - transition: all 300ms; } - .item .token-open { - margin-left: 20px; } - .item .discouraged { - text-decoration: line-through; } - .item .declaration-note { - font-size: .85em; - color: gray; - font-style: italic; } - -.pointer-container { - border-bottom: 1px solid #e2e2e2; - left: -23px; - padding-bottom: 13px; - position: relative; - width: 110%; } - -.pointer { - background: #f9f9f9; - border-left: 1px solid #e2e2e2; - border-top: 1px solid #e2e2e2; - height: 12px; - left: 21px; - top: -7px; - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); - position: absolute; - width: 12px; } - -.height-container { - display: none; - left: -25px; - padding: 0 25px; - position: relative; - width: 100%; - overflow: hidden; } - .height-container .section { - background: #f9f9f9; - border-bottom: 1px solid #e2e2e2; - left: -25px; - position: relative; - width: 100%; - padding-top: 10px; - padding-bottom: 5px; } - -.aside, .language { - padding: 6px 12px; - margin: 12px 0; - border-left: 5px solid #dddddd; - overflow-y: hidden; } - .aside .aside-title, .language .aside-title { - font-size: 9px; - letter-spacing: 2px; - text-transform: uppercase; - padding-bottom: 0; - margin: 0; - color: #aaa; - -webkit-user-select: none; } - .aside p:last-child, .language p:last-child { - margin-bottom: 0; } - -.language { - border-left: 5px solid #cde9f4; } - .language .aside-title { - color: #4b8afb; } - -.aside-warning, .aside-deprecated, .aside-unavailable { - border-left: 5px solid #ff6666; } - .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title { - color: #ff0000; } - -.graybox { - border-collapse: collapse; - width: 100%; } - .graybox p { - margin: 0; - word-break: break-word; - min-width: 50px; } - .graybox td { - border: 1px solid #e2e2e2; - padding: 5px 25px 5px 10px; - vertical-align: middle; } - .graybox tr td:first-of-type { - text-align: right; - padding: 7px; - vertical-align: top; - word-break: normal; - width: 40px; } - -.slightly-smaller { - font-size: 0.9em; } - -#footer { - position: relative; - top: 10px; - bottom: 0px; - margin-left: 25px; } - #footer p { - margin: 0; - color: #aaa; - font-size: 0.8em; } - -html.dash header, html.dash #breadcrumbs, html.dash .sidebar { - display: none; } - -html.dash .main-content { - width: 980px; - margin-left: 0; - border: none; - width: 100%; - top: 0; - padding-bottom: 0; } - -html.dash .height-container { - display: block; } - -html.dash .item .token { - margin-left: 0; } - -html.dash .content-wrapper { - width: auto; } - -html.dash #footer { - position: static; } - -form[role=search] { - float: right; } - form[role=search] input { - font: Helvetica, freesans, Arial, sans-serif; - margin-top: 6px; - font-size: 13px; - line-height: 20px; - padding: 0px 10px; - border: none; - border-radius: 1em; } - .loading form[role=search] input { - background: white url(../img/spinner.gif) center right 4px no-repeat; } - form[role=search] .tt-menu { - margin: 0; - min-width: 300px; - background: #fff; - color: #333; - border: 1px solid #e2e2e2; - z-index: 4; } - form[role=search] .tt-highlight { - font-weight: bold; } - form[role=search] .tt-suggestion { - font: Helvetica, freesans, Arial, sans-serif; - font-size: 14px; - padding: 0 8px; } - form[role=search] .tt-suggestion span { - display: table-cell; - white-space: nowrap; } - form[role=search] .tt-suggestion .doc-parent-name { - width: 100%; - text-align: right; - font-weight: normal; - font-size: 0.9em; - padding-left: 16px; } - form[role=search] .tt-suggestion:hover, - form[role=search] .tt-suggestion.tt-cursor { - cursor: pointer; - background-color: #4183c4; - color: #fff; } - form[role=search] .tt-suggestion:hover .doc-parent-name, - form[role=search] .tt-suggestion.tt-cursor .doc-parent-name { - color: #fff; } diff --git a/docs/docs/badge.svg b/docs/docs/badge.svg deleted file mode 100644 index a096fec..0000000 --- a/docs/docs/badge.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - documentation - - - documentation - - - 100% - - - 100% - - - diff --git a/docs/docsets/.docset/Contents/Info.plist b/docs/docsets/.docset/Contents/Info.plist deleted file mode 100644 index 61863ec..0000000 --- a/docs/docsets/.docset/Contents/Info.plist +++ /dev/null @@ -1,20 +0,0 @@ - - - - - CFBundleIdentifier - com.jazzy. - CFBundleName - - DocSetPlatformFamily - - isDashDocset - - dashIndexFilePath - index.html - isJavaScriptEnabled - - DashDocSetFamily - dashtoc - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes.html deleted file mode 100644 index 3e09aed..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes.html +++ /dev/null @@ -1,548 +0,0 @@ - - - - Classes Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Classes

-

The following classes are available globally.

- -
-
-
-
    -
  • -
    - - - - BinaryDecoder - -
    -
    -
    -
    -
    -
    -

    An encoder to convert binary data back to Codable objects.

    - -

    To decode from data, instantiate a decoder and specify the type:

    -
    let decoder = BinaryDecoder()
    -let message = try decoder.decode(Message.self, from: data)
    -
    - -

    Alternatively, the type can be inferred from context:

    -
    func decode(data: Data) throws -> Message {
    -    try BinaryDecoder().decode(from: data)
    -}
    -
    - -

    There are also convenience functions to directly decode a single instance:

    -
    let message = try BinaryDecoder.decode(Message.self, from: data)
    -
    -
    -

    Note

    - A single decoder can be used to decode multiple messages. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryDecoder
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryEncoder - -
    -
    -
    -
    -
    -
    -

    An encoder to convert Codable objects to binary data.

    - -

    Construct an encoder when converting instances to binary data, and feed the message(s) into it:

    -
    let message: Message = ...
    -
    -let encoder = BinaryEncoder()
    -let data = try encoder.encode(message)
    -
    -
    -

    Note

    - An ecoder can be used to encode multiple messages. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryEncoder
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryFileDecoder - -
    -
    -
    -
    -
    -
    -

    Read elements from a binary file.

    - -

    The decoder allows reading individual elements from a file without loading all file data to memory all at once. -This decreases memory usage, which is especially useful for large files. -Elements can also be read all at once, and corrupted files can be read until the first decoding error occurs.

    - -

    The class internally uses BinaryStreamDecoder to encode the individual elements, -which can also be used independently to decode the data for more complex operations.

    - -

    Handling corrupted data

    - -

    The binary format does not necessarily allow detection of data corruption, and various errors can occur -as the result of added, changed, or missing bytes. Additional measures should be applied if there is an -increased risk of data corruption.

    - -

    As an example, consider the simple encoding of a String inside a struct, which consists of a key -followed by the length of the string in bytes, and the string content. The length of the string is encoded using -variable length encoding, so a single bit flip (in the MSB of the length byte) could result in a very large length being decoded, -causing the decoder to wait for a very large number of bytes to decode the string. This simple error would cause much -data to be skipped. At the same time, it is not possible to determine with certainty where the error occured.

    - -

    The library does therefore only provide hints about the decoding errors likely occuring from non-conformance to the binary format -or version incompatibility, which are not necessarily the true causes of the failures when data corruption is present.

    -
    -

    Note

    - This class is compatible with BinaryFileEncoder and BinaryStreamEncoder, -but not with the outputs of BinaryEncoder. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryFileDecoder<Element> where Element : Decodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryFileEncoder - -
    -
    -
    -
    -
    -
    -

    Encode a stream of elements to a binary file.

    - -

    This class complements BinaryStreamEncoder to directly write encoded elements to a file.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryFileEncoder<Element> where Element : Encodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryStreamDecoder - -
    -
    -
    -
    -
    -
    -

    Decode elements from a byte stream.

    - -

    Stream decoding can be used when either the data is not regularly available completely (e.g. when loading data over a network connection), -or when the binary data should not be loaded into memory all at once (e.g. when parsing a file).

    - -

    Each stream decoder handles only elements of a single type. -The decoder can then attempt to read elements whenever new data is received, until a complete element can be decoded. -Buffering is handled internally, freeing the stream provider from this responsibility.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryStreamDecoder<Element> where Element : Decodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - BinaryStreamEncoder - -
    -
    -
    -
    -
    -
    -

    Encode elements sequentially into a binary data stream.

    - -

    A stream encoder is used to encode individual elements of the same type to a continuous binary stream, -which can be decoded sequentially.

    - -

    The encoding behaviour is different to BinaryEncoder, where the full data must be present to successfully decode. -Additional information is embedded into the stream to facilitate this behaviour. -The binary data produced by a stream encoder is not compatible with BinaryDecoder and can only be decoded using -BinaryStreamDecoder.

    - -

    The special data format of an encoded stream also allows joining sequences of encoded data, where: -encode([a,b]) + encode([c,d]) == encode([a,b,c,d]) and decode(encode([a]) + encode([b])) == [a,b]

    - -

    Example:

    -
    let encoder = BinaryStreamEncoder<Int>()
    -let encoded1 = try encoder.encode(1)
    -
    -let decoder = BinaryStreamDecoder<Int>()
    -let decoded1 = try decoder.decode(encoded1)
    -print(decoded1) // [1]
    -
    -let encoded2 = try encoder.encode(contentsOf: [2,3])
    -let decoded2 = try decoder.decode(encoded2)
    -print(decoded2) // [2,3]
    -
    -
    -

    Note

    - Stream decoders always work on a single type, because no type information is encoded into the data. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class BinaryStreamEncoder<Element> where Element : Encodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtobufDecoder - -
    -
    -
    -
    -
    -
    -

    An encoder to convert protobuf binary data back to Codable objects.

    - -

    Decoding unsupported data types causes DecodingError or ProtobufDecodingError errors.

    - -

    To decode from data, instantiate a decoder and specify the type:

    -
    let decoder = BinaryDecoder()
    -let message = try decoder.decode(Message.self, from: data)
    -
    - -

    Alternatively, the type can be inferred from context:

    -
    func decode(data: Data) throws -> Message {
    -    try BinaryDecoder().decode(from: data)
    -}
    -
    - -

    There are also convenience functions to directly decode a single instance:

    -
    let message = try BinaryDecoder.decode(Message.self, from: data)
    -
    -
    -

    Note

    - A single decoder can be used to decode multiple messages. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class ProtobufDecoder
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtobufEncoder - -
    -
    -
    -
    -
    -
    -

    An encoder to convert Codable objects to protobuf binary data.

    - -

    The encoder provides only limited compatibility with Google’s Protocol Buffers.

    - -

    Encoding unsupported data types causes ProtobufEncodingError errors.

    - -

    Construct an encoder when converting instances to binary data, and feed the message(s) into it:

    -
    let message: Message = ...
    -
    -let encoder = ProtobufEncoder()
    -let data = try encoder.encode(message)
    -
    -
    -

    Note

    - An ecoder can be used to encode multiple messages. - -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public final class ProtobufEncoder
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryDecoder.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryDecoder.html deleted file mode 100644 index 1265412..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryDecoder.html +++ /dev/null @@ -1,449 +0,0 @@ - - - - BinaryDecoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryDecoder

-
-
- -
public final class BinaryDecoder
- -
-
-

An encoder to convert binary data back to Codable objects.

- -

To decode from data, instantiate a decoder and specify the type:

-
let decoder = BinaryDecoder()
-let message = try decoder.decode(Message.self, from: data)
-
- -

Alternatively, the type can be inferred from context:

-
func decode(data: Data) throws -> Message {
-    try BinaryDecoder().decode(from: data)
-}
-
- -

There are also convenience functions to directly decode a single instance:

-
let message = try BinaryDecoder.decode(Message.self, from: data)
-
-
-

Note

- A single decoder can be used to decode multiple messages. - -
- -
-
-
-
    -
  • -
    - - - - userInfo - -
    -
    -
    -
    -
    -
    -

    Any contextual information set by the user for decoding.

    - -

    This dictionary is passed to all containers during the decoding process.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var userInfo: [CodingUserInfoKey : Any]
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Assumes that unkeyed containers are encoded using a set of indices for nil values.

    - -

    Refer to the prependNilIndexSetForUnkeyedContainers property of BinaryEncoder -for more information about the binary data format in both cases.

    -
    -

    Note

    - This option defaults to false - -
    -

    Note

    - To decode successfully, the encoder must use the same setting for prependNilIndexSetForUnkeyedContainers. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var containsNilIndexSetForUnkeyedContainers: Bool
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Create a new binary encoder.

    -
    -

    Note

    - A single decoder can be used to decode multiple messages. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - decode(_:from:) - -
    -
    -
    -
    -
    -
    -

    Decode a type from binary data.

    -
    -

    Throws

    - Errors of type DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decode<T>(_ type: T.Type = T.self, from data: Data) throws -> T where T : Decodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - type - - -
    -

    The type to decode.

    -
    -
    - - data - - -
    -

    The binary data which encodes the instance

    -
    -
    -
    -
    -

    Return Value

    -

    The decoded instance

    -
    -
    -
    -
  • -
  • -
    - - - - decode(_:from:) - -
    -
    -
    -
    -
    -
    -

    Decode a single value from binary data using a default decoder.

    -
    -

    Throws

    - Errors of type DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func decode<T>(_ type: T.Type = T.self, from data: Data) throws -> T where T : Decodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - type - - -
    -

    The type to decode.

    -
    -
    - - data - - -
    -

    The binary data which encodes the instance

    -
    -
    -
    -
    -

    Return Value

    -

    The decoded instance

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryEncoder.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryEncoder.html deleted file mode 100644 index 0cf577f..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryEncoder.html +++ /dev/null @@ -1,477 +0,0 @@ - - - - BinaryEncoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryEncoder

-
-
- -
public final class BinaryEncoder
- -
-
-

An encoder to convert Codable objects to binary data.

- -

Construct an encoder when converting instances to binary data, and feed the message(s) into it:

-
let message: Message = ...
-
-let encoder = BinaryEncoder()
-let data = try encoder.encode(message)
-
-
-

Note

- An ecoder can be used to encode multiple messages. - -
- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    Sort keyed data in the binary representation.

    - -

    Enabling this option causes all keyed data (e.g. Dictionary, Struct) to be sorted by their keys before encoding. -This option can enable deterministic encoding where the binary output is consistent across multiple invocations.

    -
    -

    Warning

    - Output will not be deterministic when using Set, or Dictionary<Key, Value> where Key is not String or Int. - -
    - -

    Enabling this option introduces computational overhead due to sorting, which can become significant when dealing with many entries.

    - -

    This option has no impact on decoding using BinaryDecoder.

    - -

    Enabling this option will add the CodingUserInfoKey(rawValue: "sort") to the userInfo dictionary.

    -
    -

    Note

    - The default value for this option is false. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var sortKeysDuringEncoding: Bool
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Add a set of indices for nil values in unkeyed containers.

    - -

    This option is only necessary for custom implementations of func encode(to:), when using encodeNil() on UnkeyedEncodingContainer.

    - -

    If this option is set to true, then the encoded binary data first contains a list of indexes for each position where nil is encoded. -After this data the remaining (non-nil) values are added. -If this option is false, then each value is prepended with a byte 1 for non-nil values, and a byte 0 for nil values.

    - -

    An index set is encoded using first the number of elements, and then each element, all encoded as var-ints.

    - -

    One benefit of this option is that top-level sequences can be joined using their binary data, where encoded([a,b]) | encoded([c,d]) == encoded([a,b,c,d]).

    -
    -

    Note

    - This option defaults to false - -
    -

    Note

    - To decode successfully, the decoder must use the same setting for containsNilIndexSetForUnkeyedContainers. - -
    -

    Note

    - Using encodeNil() on UnkeyedEncodingContainer without this option results in a fatal error. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var prependNilIndexSetForUnkeyedContainers: Bool
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - userInfo - -
    -
    -
    -
    -
    -
    -

    Any contextual information set by the user for encoding.

    - -

    This dictionary is passed to all containers during the encoding process.

    - -

    Contains also keys for any custom options set for the encoder. -See sortKeysDuringEncoding.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var userInfo: [CodingUserInfoKey : Any]
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Create a new binary encoder.

    -
    -

    Note

    - An encoder can be used to encode multiple messages. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode a value to binary data.

    -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode(_ value: Encodable) throws -> Data
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - value - - -
    -

    The value to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The encoded data

    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode a single value to binary data using a default encoder.

    -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func encode(_ value: Encodable) throws -> Data
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - value - - -
    -

    The value to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The encoded data

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryFileDecoder.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryFileDecoder.html deleted file mode 100644 index b40ae09..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryFileDecoder.html +++ /dev/null @@ -1,495 +0,0 @@ - - - - BinaryFileDecoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryFileDecoder

-
-
- -
public final class BinaryFileDecoder<Element> where Element : Decodable
- -
-
-

Read elements from a binary file.

- -

The decoder allows reading individual elements from a file without loading all file data to memory all at once. -This decreases memory usage, which is especially useful for large files. -Elements can also be read all at once, and corrupted files can be read until the first decoding error occurs.

- -

The class internally uses BinaryStreamDecoder to encode the individual elements, -which can also be used independently to decode the data for more complex operations.

- -

Handling corrupted data

- -

The binary format does not necessarily allow detection of data corruption, and various errors can occur -as the result of added, changed, or missing bytes. Additional measures should be applied if there is an -increased risk of data corruption.

- -

As an example, consider the simple encoding of a String inside a struct, which consists of a key -followed by the length of the string in bytes, and the string content. The length of the string is encoded using -variable length encoding, so a single bit flip (in the MSB of the length byte) could result in a very large length being decoded, -causing the decoder to wait for a very large number of bytes to decode the string. This simple error would cause much -data to be skipped. At the same time, it is not possible to determine with certainty where the error occured.

- -

The library does therefore only provide hints about the decoding errors likely occuring from non-conformance to the binary format -or version incompatibility, which are not necessarily the true causes of the failures when data corruption is present.

-
-

Note

- This class is compatible with BinaryFileEncoder and BinaryStreamEncoder, -but not with the outputs of BinaryEncoder. - -
- -
-
-
-
    -
  • -
    - - - - init(fileAt:decoder:) - -
    -
    -
    -
    -
    -
    -

    Create a file decoder.

    - -

    The given file is opened, and decoding will begin at the start of the file.

    -
    -

    Throws

    - An error, if the file handle could not be created. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fileAt url: URL, decoder: BinaryDecoder = .init()) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - url - - -
    -

    The url of the file to read.

    -
    -
    - - decoder - - -
    -

    The decoder to use for decoding

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    Close the file.

    -
    -

    Note

    - After closing the file, the decoder can no longer read elements, which will result in an error or an exception. - -
    -

    Throws

    - Currently throws a ObjC-style Exception, not an Error, even on modern systems. -This is a bug in the Foundation framework. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close() throws
    - -
    -
    -
    -
    -
  • -
-
-
-
- - -
- -

Decoding -

-
-
-
    -
  • -
    - - - - read(_:) - -
    -
    -
    -
    -
    -
    -

    Read all elements in the file, and handle each element using a closure.

    -
    -

    Throws

    - Decoding errors of type DecodingError. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func read(_ elementHandler: (Element) throws -> Void) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - elementHandler - - -
    -

    The closure to handle each element as it is decoded.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - readAll() - -
    -
    -
    -
    -
    -
    -

    Read all elements at once.

    -
    -

    Throws

    - Errors of type DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func readAll() throws -> [Element]
    - -
    -
    -
    -

    Return Value

    -

    The elements decoded from the file.

    -
    -
    -
    -
  • -
  • -
    - - - - readAllUntilError() - -
    -
    -
    -
    -
    -
    -

    Read all elements at once, and ignore errors.

    - -

    This function reads elements until it reaches the end of the file or detects a decoding error. -Any data after the first error will be ignored.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func readAllUntilError() -> [Element]
    - -
    -
    -
    -

    Return Value

    -

    The elements successfully decoded from the file.

    -
    -
    -
    -
  • -
  • -
    - - - - readElement() - -
    -
    -
    -
    -
    -
    -

    Read a single elements from the current position in the file.

    -
    -

    Throws

    - Errors of type DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func readElement() throws -> Element?
    - -
    -
    -
    -

    Return Value

    -

    The element decoded from the file, or nil, if no more data is available.

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryFileEncoder.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryFileEncoder.html deleted file mode 100644 index 1adea8b..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryFileEncoder.html +++ /dev/null @@ -1,410 +0,0 @@ - - - - BinaryFileEncoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryFileEncoder

-
-
- -
public final class BinaryFileEncoder<Element> where Element : Encodable
- -
-
-

Encode a stream of elements to a binary file.

- -

This class complements BinaryStreamEncoder to directly write encoded elements to a file.

- -
-
-
-
    -
  • -
    - - - - init(fileAt:encoder:) - -
    -
    -
    -
    -
    -
    -

    Create a new file encoder.

    -
    -

    Note

    - The file will be created, if it does not exist. -If it exists, then the new elements will be appended to the end of the file. - -
    -

    Throws

    - Throws an error if the file could not be accessed or created. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fileAt url: URL, encoder: BinaryEncoder = .init()) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - url - - -
    -

    The url to the file.

    -
    -
    - - encoder - - -
    -

    The encoder to use for each element.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    Close the file.

    -
    -

    Note

    - After closing the file, the encoder can no longer write elements, which will result in an error or an exception. - -
    -

    Throws

    - Currently throws a ObjC-style Exception, not an Error, even on modern systems. -This is a bug in the Foundation framework. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close() throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - write(_:) - -
    -
    -
    -
    -
    -
    -

    Write a single element to the file.

    -
    -

    Note

    - This function will throw an error or exception if the file handle has already been closed. - -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func write(_ element: Element) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - element - - -
    -

    The element to encode.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - write(contentsOf:) - -
    -
    -
    -
    -
    -
    -

    Write a sequence of elements to the file.

    - -

    This is a convenience function calling write(_ element:) for each element of the sequence in order.

    -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func write<S>(contentsOf sequence: S) throws where Element == S.Element, S : Sequence
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - sequence - - -
    -

    The sequence to encode

    -
    -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryStreamDecoder.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryStreamDecoder.html deleted file mode 100644 index cc98207..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryStreamDecoder.html +++ /dev/null @@ -1,535 +0,0 @@ - - - - BinaryStreamDecoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryStreamDecoder

-
-
- -
public final class BinaryStreamDecoder<Element> where Element : Decodable
- -
-
-

Decode elements from a byte stream.

- -

Stream decoding can be used when either the data is not regularly available completely (e.g. when loading data over a network connection), -or when the binary data should not be loaded into memory all at once (e.g. when parsing a file).

- -

Each stream decoder handles only elements of a single type. -The decoder can then attempt to read elements whenever new data is received, until a complete element can be decoded. -Buffering is handled internally, freeing the stream provider from this responsibility.

- -
-
-
-
    -
  • -
    - - - - init(decoder:) - -
    -
    -
    -
    -
    -
    -

    Create a stream decoder.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(decoder: BinaryDecoder = .init())
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - decoder - - -
    -

    The decoder to use for decoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - add(_:) - -
    -
    -
    -
    -
    -
    -

    Add new data to the internal buffer without attempting to decode elements.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func add(_ data: Data)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - data - - -
    -

    The datat to append to the internal buffer.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - discardBytes(_:) - -
    -
    -
    -
    -
    -
    -

    Discard bytes from the currently remaining bytes.

    - -

    This function can be used when attempting to correct a corrupted stream. It may be possible to drop bytes from the buffer until a new valid element can be decoded.

    -
    -

    Note

    - If the stream data is corrupted, then decoding elements from the middle of the stream may lead to unexpected results. -It’s possible to decode bogus elements when starting at the wrong position, or the stream decoder could get stuck waiting for a large number of bytes. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    @discardableResult
    -public func discardBytes(_ count: Int) -> Int
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - count - - -
    -

    The number of bytes to remove

    -
    -
    -
    -
    -

    Return Value

    -

    The number of bytes removed.

    -
    -
    -
    -
  • -
-
-
-
- - -
- -

Decoding -

-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    Read elements from the stream until no more bytes are available.

    -
    -

    Throws

    - Decoding errors of type DecodingError. - -
    -

    Note

    - This function is deprecated. If you want to decode elements, use decodeElements(), or use decodeElementsUntilError(), if you expect errors in the data stream. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    @available(*, deprecated, message: "Use add(﹚ and decodeElementsUntilError(﹚ or decodeElements(﹚ instead.")
    -public func decode(_ data: Data, returnElementsBeforeError: Bool = false) throws -> [Element]
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - returnElementsBeforeError - - -
    -

    If set to true, -then all successfully decoded elements will be returned if an error occurs. Defaults to false

    -
    -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Read elements from the stream until an error occurs.

    - -

    This function may be useful if the data is corrupted. The readable elements can be decoded until a decoding error is encountered.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decodeElementsUntilError() -> (elements: [Element], error: Error?)
    - -
    -
    -
    -

    Return Value

    -

    The elements that could be decoded until an error occured or the buffer was insufficient, and the decoding error, if one occured.

    -
    -
    -
    -
  • -
  • -
    - - - - decodeElements() - -
    -
    -
    -
    -
    -
    -

    Read elements until no more data is available.

    -
    -

    Note

    - If a decoding error occurs within the data, then the elements successfully decoded before are lost. -If you expect decoding errors to occur, either decode elements individually using decodeElement(), or use decodeElementsUntilError(). - -
    -

    Throws

    - Decoding errors of type DecodingError. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decodeElements() throws -> [Element]
    - -
    -
    -
    -

    Return Value

    -

    The elements that could be decoded with the available data.

    -
    -
    -
    -
  • -
  • -
    - - - - decodeElement() - -
    -
    -
    -
    -
    -
    -

    Attempt to decode a single element from the stream.

    - -

    If insufficient bytes are available, then no element is returned, and the internal buffer is kept intact.

    -
    -

    Throws

    - Decoding errors of type DecodingError. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decodeElement() throws -> Element?
    - -
    -
    -
    -

    Return Value

    -

    The decoded element, if enough bytes are available.

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryStreamEncoder.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryStreamEncoder.html deleted file mode 100644 index 31dd421..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BinaryStreamEncoder.html +++ /dev/null @@ -1,399 +0,0 @@ - - - - BinaryStreamEncoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryStreamEncoder

-
-
- -
public final class BinaryStreamEncoder<Element> where Element : Encodable
- -
-
-

Encode elements sequentially into a binary data stream.

- -

A stream encoder is used to encode individual elements of the same type to a continuous binary stream, -which can be decoded sequentially.

- -

The encoding behaviour is different to BinaryEncoder, where the full data must be present to successfully decode. -Additional information is embedded into the stream to facilitate this behaviour. -The binary data produced by a stream encoder is not compatible with BinaryDecoder and can only be decoded using -BinaryStreamDecoder.

- -

The special data format of an encoded stream also allows joining sequences of encoded data, where: -encode([a,b]) + encode([c,d]) == encode([a,b,c,d]) and decode(encode([a]) + encode([b])) == [a,b]

- -

Example:

-
let encoder = BinaryStreamEncoder<Int>()
-let encoded1 = try encoder.encode(1)
-
-let decoder = BinaryStreamDecoder<Int>()
-let decoded1 = try decoder.decode(encoded1)
-print(decoded1) // [1]
-
-let encoded2 = try encoder.encode(contentsOf: [2,3])
-let decoded2 = try decoder.decode(encoded2)
-print(decoded2) // [2,3]
-
-
-

Note

- Stream decoders always work on a single type, because no type information is encoded into the data. - -
- -
-
-
-
    -
  • -
    - - - - init(encoder:) - -
    -
    -
    -
    -
    -
    -

    Create a new stream encoder.

    -
    -

    Note

    - The encoder should never be reconfigured after being passed to this function, -to prevent decoding errors due to mismatching binary formats. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(encoder: BinaryEncoder = .init())
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - encoder - - -
    -

    The encoder to use for the individual elements.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode an element for the data stream.

    - -

    Call this function to convert an element into binary data whenever new elements are available. -The data provided as the result of this function should be processed (e.g. stored or transmitted) while conserving the -order of the chunks, so that decoding can work reliably.

    - -

    Pass the encoded data back into an instance of BinaryStreamDecoder to convert each chunk back to an element.

    -
    -

    Note

    - Data encoded by this function can only be decoded by an appropriate BinaryStreamDecoder. -Decoding using a simple BinaryDecoder will not be successful. - -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode(_ element: Element) throws -> Data
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - element - - -
    -

    The element to encode.

    -
    -
    -
    -
    -

    Return Value

    -

    The next chunk of the encoded binary stream.

    -
    -
    -
    -
  • -
  • -
    - - - - encode(contentsOf:) - -
    -
    -
    -
    -
    -
    -

    Encode a sequence of elements.

    - -

    This function performs multiple calls to encode(_:) to convert all elements of the sequence, and then returns the joined data.

    -
    -

    Throws

    - Errors of type EncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode<S>(contentsOf sequence: S) throws -> Data where Element == S.Element, S : Sequence
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - sequence - - -
    -

    The sequence of elements to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The binary data of the encoded sequence elements

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/ProtobufDecoder.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/ProtobufDecoder.html deleted file mode 100644 index b0deaeb..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/ProtobufDecoder.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - ProtobufDecoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

ProtobufDecoder

-
-
- -
public final class ProtobufDecoder
- -
-
-

An encoder to convert protobuf binary data back to Codable objects.

- -

Decoding unsupported data types causes DecodingError or ProtobufDecodingError errors.

- -

To decode from data, instantiate a decoder and specify the type:

-
let decoder = BinaryDecoder()
-let message = try decoder.decode(Message.self, from: data)
-
- -

Alternatively, the type can be inferred from context:

-
func decode(data: Data) throws -> Message {
-    try BinaryDecoder().decode(from: data)
-}
-
- -

There are also convenience functions to directly decode a single instance:

-
let message = try BinaryDecoder.decode(Message.self, from: data)
-
-
-

Note

- A single decoder can be used to decode multiple messages. - -
- -
-
-
-
    -
  • -
    - - - - userInfo - -
    -
    -
    -
    -
    -
    -

    Any contextual information set by the user for decoding.

    - -

    This dictionary is passed to all containers during the decoding process.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var userInfo: [CodingUserInfoKey : Any]
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Create a new binary encoder.

    -
    -

    Note

    - A single decoder can be used to decode multiple messages. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - decode(_:from:) - -
    -
    -
    -
    -
    -
    -

    Decode a type from binary data.

    -
    -

    Throws

    - Errors of type DecodingError or ProtobufDecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func decode<T>(_ type: T.Type = T.self, from data: Data) throws -> T where T : Decodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - type - - -
    -

    The type to decode.

    -
    -
    - - data - - -
    -

    The binary data which encodes the instance

    -
    -
    -
    -
    -

    Return Value

    -

    The decoded instance

    -
    -
    -
    -
  • -
  • -
    - - - - decode(_:from:) - -
    -
    -
    -
    -
    -
    -

    Decode a single value from binary data using a default decoder.

    -
    -

    Throws

    - Errors of type DecodingError or ProtobufDecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func decode<T>(_ type: T.Type = T.self, from data: Data) throws -> T where T : Decodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - type - - -
    -

    The type to decode.

    -
    -
    - - data - - -
    -

    The binary data which encodes the instance

    -
    -
    -
    -
    -

    Return Value

    -

    The decoded instance

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/ProtobufEncoder.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/ProtobufEncoder.html deleted file mode 100644 index b3956f9..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/ProtobufEncoder.html +++ /dev/null @@ -1,385 +0,0 @@ - - - - ProtobufEncoder Class Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

ProtobufEncoder

-
-
- -
public final class ProtobufEncoder
- -
-
-

An encoder to convert Codable objects to protobuf binary data.

- -

The encoder provides only limited compatibility with Google’s Protocol Buffers.

- -

Encoding unsupported data types causes ProtobufEncodingError errors.

- -

Construct an encoder when converting instances to binary data, and feed the message(s) into it:

-
let message: Message = ...
-
-let encoder = ProtobufEncoder()
-let data = try encoder.encode(message)
-
-
-

Note

- An ecoder can be used to encode multiple messages. - -
- -
-
-
-
    -
  • -
    - - - - userInfo - -
    -
    -
    -
    -
    -
    -

    Any contextual information set by the user for encoding.

    - -

    This dictionary is passed to all containers during the encoding process.

    - -

    Contains also keys for any custom options set for the encoder. -See sortKeysDuringEncoding.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var userInfo: [CodingUserInfoKey : Any]
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Create a new binary encoder.

    -
    -

    Note

    - An encoder can be used to encode multiple messages. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode a value to binary data.

    -
    -

    Throws

    - Errors of type EncodingError or ProtobufEncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode<T>(_ value: T) throws -> Data where T : Encodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - value - - -
    -

    The value to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The encoded data

    -
    -
    -
    -
  • -
  • -
    - - - - encode(_:) - -
    -
    -
    -
    -
    -
    -

    Encode a single value to binary data using a default encoder.

    -
    -

    Throws

    - Errors of type EncodingError or ProtobufEncodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func encode<T>(_ value: T) throws -> Data where T : Encodable
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - value - - -
    -

    The value to encode

    -
    -
    -
    -
    -

    Return Value

    -

    The encoded data

    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums.html deleted file mode 100644 index 1b4f72d..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums.html +++ /dev/null @@ -1,276 +0,0 @@ - - - - Enumerations Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Enumerations

-

The following enumerations are available globally.

- -
-
-
-
    -
  • -
    - - - - DataType - -
    -
    -
    -
    -
    -
    -

    The data type specifying how a value is encoded on the wire.

    - -

    The data type is mixed into the key for each value to indicate how many bytes the value occupies in the following bytes.

    - -

    The data type is equivalent to the Protocol Buffer Wire Type, but extended to support more data types.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum DataType : Int
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtobufDecodingError - -
    -
    -
    -
    -
    -
    -

    An error produced while decoding binary data.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum ProtobufDecodingError : Error
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtobufEncodingError - -
    -
    -
    -
    -
    -
    -

    An error thrown when encoding a value using ProtobufEncoder.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum ProtobufEncodingError : Error
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/BinaryDecodingError.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/BinaryDecodingError.html deleted file mode 100644 index 39653e3..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/BinaryDecodingError.html +++ /dev/null @@ -1,532 +0,0 @@ - - - - BinaryDecodingError Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryDecodingError

-
-
- -
public enum BinaryDecodingError : Error
- -
-
-

An error produced while decoding binary data.

- -
-
-
-
    -
  • -
    - - - - invalidDataSize - -
    -
    -
    -
    -
    -
    -

    The data for a primitive type did not have the right size.

    -
    -

    Note

    - This error is internal and should not occur in practice. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidDataSize
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - missingDataForKey(_:) - -
    -
    -
    -
    -
    -
    -

    The binary data is missing for a key.

    - -

    The associated value is the CodingKey not found in the data.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case missingDataForKey(CodingKey)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unknownDataType(_:) - -
    -
    -
    -
    -
    -
    -

    The binary data contained an unknown data type.

    - -

    The associated value is the unknown raw value of the data type.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unknownDataType(Int)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - prematureEndOfData - -
    -
    -
    -
    -
    -
    -

    The binary data ended before all values were decoded.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case prematureEndOfData
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - invalidString - -
    -
    -
    -
    -
    -
    -

    A String contained in the data could not be decoded.

    - -

    The failing string can be either a value, or a string key (e.g. a property name or enum case).

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidString
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    An integer encoded in the binary data as a varint does not fit into the specified integer type, producing an overflow.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case variableLengthEncodedIntegerOutOfRange
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - multipleValuesForKey - -
    -
    -
    -
    -
    -
    -

    The binary data contains multiple values for a key.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleValuesForKey
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - dataCorrupted(_:) - -
    -
    -
    -
    -
    -
    -

    An indication that the data is corrupted or otherwise invalid.

    - -

    As an associated value, this case contains the context for debugging.

    - -

    This error can occur when an unknown enum value was decoded.

    -
    -

    Note

    - This error case mirrors DecodingError.dataCorrupted() - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case dataCorrupted(DecodingError.Context)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - typeMismatch(_:_:) - -
    -
    -
    -
    -
    -
    -

    An indication that a value of the given type could not be decoded because it did not match the type of what was found in the encoded payload.

    - -

    As associated values, this case contains the attempted type and context for debugging.

    -
    -

    Note

    - This error case mirrors DecodingError.typeMismatch() - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case typeMismatch(Any.Type, DecodingError.Context)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - valueNotFound(_:_:) - -
    -
    -
    -
    -
    -
    -

    An indication that a non-optional value of the given type was expected, but a null value was found.

    -
    -

    Note

    - This error case mirrors DecodingError.valueNotFound() - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case valueNotFound(Any.Type, DecodingError.Context)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unknownError(_:) - -
    -
    -
    -
    -
    -
    -

    An unexpected and unknown error occured during decoding.

    - -

    As the associated value, this case contains the original error.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unknownError(Error)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/BinaryEncodingError.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/BinaryEncodingError.html deleted file mode 100644 index 4003b3b..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/BinaryEncodingError.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - BinaryEncodingError Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

BinaryEncodingError

-
-
- -
public enum BinaryEncodingError : Error
- -
-
-

An error thrown when encoding a value using BinaryEncoder.

- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    A string could not be encoded to UTF-8.

    - -

    The associated value of the error is the failed string.

    - -

    The string can either be a string key (e.g. a property or enum case name), or a String value.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case stringEncodingFailed(String)
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    A procedural error occuring during encoding.

    - -

    A custom implementation of func encode(to encoder: Encoder) throws tried to encode multiple values into a single value container:

    -
    func encode(to encoder: Encoder) throws {
    -   var container = encoder.singleValueContainer()
    -   container.encode(...)
    -   container.encode(...) // Invalid
    -}
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleValuesInSingleValueContainer
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - invalidValue(_:_:) - -
    -
    -
    -
    -
    -
    -

    An indication that an encoder or its containers could not encode the given value.

    - -

    As associated values, this case contains the attempted value and context for debugging.

    -
    -

    Note

    - This error case mirrors EncodingError.invalidValue() - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidValue(Any, EncodingError.Context)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unknownError(_:) - -
    -
    -
    -
    -
    -
    -

    An unexpected and unknown error occured during encoding.

    - -

    As the associated value, this case contains the original error.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unknownError(Error)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/DataType.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/DataType.html deleted file mode 100644 index 9948cdf..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/DataType.html +++ /dev/null @@ -1,391 +0,0 @@ - - - - DataType Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

DataType

-
-
- -
public enum DataType : Int
- -
-
-

The data type specifying how a value is encoded on the wire.

- -

The data type is mixed into the key for each value to indicate how many bytes the value occupies in the following bytes.

- -

The data type is equivalent to the Protocol Buffer Wire Type, but extended to support more data types.

- -
-
-
-
    -
  • -
    - - - - variableLengthInteger - -
    -
    -
    -
    -
    -
    -

    An integer value encoded as a Base128 Varint. -The length can be determined by reading the value, -where the first (MSB) indicates whether another byte follows. -A Varint consumes up to 9 bytes, where all bits from the last byte are used, -resulting in 8 * 7 + 8 = 64 usable bits.

    - -

    For Protobuf encoding, the Varint can use up to 10 bytes, since the MSB is used for all bytes, including byte 9.

    - -

    See also Protocol Buffers: Base 128 Varints.

    - -

    Used for: Int, Int32, Int64, UInt, UInt32, UInt64, Bool

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case variableLengthInteger = 0
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - byte - -
    -
    -
    -
    -
    -
    -

    The value is encoded as a single byte.

    -
    -

    Note

    - This data type is incompatible with the protocol buffer specification. - -
    - -

    Used for: UInt8, Int8

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case byte = 6
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - twoBytes - -
    -
    -
    -
    -
    -
    -

    The value is encoded as two bytes.

    -
    -

    Note

    - This data type is incompatible with the protocol buffer specification. - -
    - -

    Used for: Int16, UInt16

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case twoBytes = 7
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - variableLength - -
    -
    -
    -
    -
    -
    -

    The value is encoded using first a length (as a UInt64 var-int) followed by the bytes.

    - -

    Used by: String, Data, complex types

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case variableLength = 2
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fourBytes - -
    -
    -
    -
    -
    -
    -

    The value is encoded using four bytes.

    - -

    Used for: Float, FixedWidth<Int32>, FixedWidth<UInt32>

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case fourBytes = 5
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - eightBytes - -
    -
    -
    -
    -
    -
    -

    The value is encoded using eight bytes.

    - -

    Used by: Double, FixedWidth<Int64>, FixedWidth<Int>, FixedWidth<UInt64>, FixedWidth<UInt>

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case eightBytes = 1
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/ProtobufDecodingError.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/ProtobufDecodingError.html deleted file mode 100644 index 5dac617..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/ProtobufDecodingError.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - ProtobufDecodingError Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

ProtobufDecodingError

-
-
- -
public enum ProtobufDecodingError : Error
- -
-
-

An error produced while decoding binary data.

- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unexpectedDictionaryKey
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - superNotSupported - -
    -
    -
    -
    -
    -
    -

    Protocol buffers don’t support inheritance, so super can’t be encoded.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case superNotSupported
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unsupportedType(_:) - -
    -
    -
    -
    -
    -
    -

    The encoded type contains a basic type that is not supported.

    - -

    The associated value contains a textual description of the unsupported type.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unsupportedType(String)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - invalidAccess(_:) - -
    -
    -
    -
    -
    -
    -

    A decoding feature was accessed which is not supported for protobuf encoding.

    - -

    The associated value contains a textual description of the invalid access.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidAccess(String)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/ProtobufEncodingError.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/ProtobufEncodingError.html deleted file mode 100644 index 86cca4c..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/ProtobufEncodingError.html +++ /dev/null @@ -1,560 +0,0 @@ - - - - ProtobufEncodingError Enumeration Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

ProtobufEncodingError

-
-
- -
public enum ProtobufEncodingError : Error
- -
-
-

An error thrown when encoding a value using ProtobufEncoder.

- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    A procedural error occuring during encoding.

    - -

    A custom implementation of func encode(to encoder: Encoder) throws tried to encode multiple values into a single value container:

    -
    func encode(to encoder: Encoder) throws {
    -   var container = encoder.singleValueContainer()
    -   container.encode(...)
    -   container.encode(...) // Invalid
    -}
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleValuesInSingleValueContainer
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case noValueInSingleValueContainer
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - nilValuesNotSupported - -
    -
    -
    -
    -
    -
    -

    The encoded type contains optional values, which are not supported in the protocol buffer format.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case nilValuesNotSupported
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - unsupportedType(_:) - -
    -
    -
    -
    -
    -
    -

    The encoded type contains a basic type that is not supported.

    - -

    The associated value contains a textual description of the unsupported type.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case unsupportedType(String)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - superNotSupported - -
    -
    -
    -
    -
    -
    -

    Protocol buffers don’t support inheritance, so super can’t be encoded.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case superNotSupported
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - missingIntegerKey(_:) - -
    -
    -
    -
    -
    -
    -

    The encoded type contains properties which don’t have an integer key.

    - -

    The associated value contains the string key which is missing an integer key.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case missingIntegerKey(String)
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    All values in unkeyed containers must have the same type.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleTypesInUnkeyedContainer
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Field numbers must be positive integers not greater than 536870911 (2^29-1, or 0x1FFFFFFF)

    - -

    The associated value is the integer key that is out of range.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case integerKeyOutOfRange(Int)
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Multiple calls to container<>(keyedBy:), unkeyedContainer(), or singleValueContainer() for an encoder.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case multipleContainersAccessed
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - noContainersAccessed - -
    -
    -
    -
    -
    -
    -

    No calls to container<>(keyedBy:), unkeyedContainer(), or singleValueContainer() for an encoder.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case noContainersAccessed
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Protobuf requires an unkeyed container as the root node

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case rootIsNotKeyedContainer
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - invalidAccess(_:) - -
    -
    -
    -
    -
    -
    -

    An unavailable encoding feature was accessed.

    - -

    The associated value contains a textual description of the unsupported access.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case invalidAccess(String)
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case protobufDefinitionUnavailable(String)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Extensions.html b/docs/docsets/.docset/Contents/Resources/Documents/Extensions.html deleted file mode 100644 index c04129e..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Extensions.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - Extensions Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Extensions

-

The following extensions are available globally.

- -
-
-
-
    -
  • -
    - - - - DecodingKey - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension DecodingKey: Equatable
    -
    extension DecodingKey: Hashable
    -
    extension DecodingKey: CustomStringConvertible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MixedCodingKeyWrapper - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension MixedCodingKeyWrapper: Equatable
    -
    extension MixedCodingKeyWrapper: Hashable
    -
    extension MixedCodingKeyWrapper: Comparable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - ProtoKeyWrapper - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension ProtoKeyWrapper: Equatable
    -
    extension ProtoKeyWrapper: Hashable
    -
    extension ProtoKeyWrapper: Comparable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - IntKeyWrapper - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension IntKeyWrapper: Hashable
    -
    extension IntKeyWrapper: Equatable
    -
    extension IntKeyWrapper: Comparable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Bool - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Bool: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Data - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Data: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Float - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Float: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Int - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Int: FixedSizeCompatible
    -
    extension Int: SignedValueCompatible
    -
    extension Int: ZigZagCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Int32 - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Int32: ZigZagCodable
    -
    extension Int32: FixedSizeCompatible
    -
    extension Int32: SignedValueCompatible
    -
    extension Int32: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Int64 - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension Int64: FixedSizeCompatible
    -
    extension Int64: SignedValueCompatible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - String - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension String: ProtobufCodable
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - UInt - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension UInt: FixedSizeCompatible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - UInt32 - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension UInt32: FixedSizeCompatible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - UInt64 - -
    -
    -
    -
    -
    -
    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    extension UInt64: FixedSizeCompatible
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/Int.html b/docs/docsets/.docset/Contents/Resources/Documents/Extensions/Int.html deleted file mode 100644 index 686c938..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/Int.html +++ /dev/null @@ -1,326 +0,0 @@ - - - - Int Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Int

-
-
- -
extension Int: FixedSizeCompatible
-
extension Int: SignedValueCompatible
-
extension Int: ZigZagCodable
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - positiveProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var positiveProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/Int32.html b/docs/docsets/.docset/Contents/Resources/Documents/Extensions/Int32.html deleted file mode 100644 index 44b5320..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/Int32.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - Int32 Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Int32

-
-
- -
extension Int32: ZigZagCodable
-
extension Int32: FixedSizeCompatible
-
extension Int32: SignedValueCompatible
-
extension Int32: ProtobufCodable
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - positiveProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var positiveProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/Int64.html b/docs/docsets/.docset/Contents/Resources/Documents/Extensions/Int64.html deleted file mode 100644 index dedfd41..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/Int64.html +++ /dev/null @@ -1,325 +0,0 @@ - - - - Int64 Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Int64

-
-
- -
extension Int64: FixedSizeCompatible
-
extension Int64: SignedValueCompatible
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - positiveProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var positiveProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/UInt.html b/docs/docsets/.docset/Contents/Resources/Documents/Extensions/UInt.html deleted file mode 100644 index 5df0857..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/UInt.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - UInt Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

UInt

-
-
- -
extension UInt: FixedSizeCompatible
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/UInt32.html b/docs/docsets/.docset/Contents/Resources/Documents/Extensions/UInt32.html deleted file mode 100644 index eceb923..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/UInt32.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - UInt32 Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

UInt32

-
-
- -
extension UInt32: FixedSizeCompatible
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/UInt64.html b/docs/docsets/.docset/Contents/Resources/Documents/Extensions/UInt64.html deleted file mode 100644 index 26bf37a..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Extensions/UInt64.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - UInt64 Extension Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

UInt64

-
-
- -
extension UInt64: FixedSizeCompatible
- -
-
- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Guides.html b/docs/docsets/.docset/Contents/Resources/Documents/Guides.html deleted file mode 100644 index 767b429..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Guides.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - Guides Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- - -
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Protocols.html b/docs/docsets/.docset/Contents/Resources/Documents/Protocols.html deleted file mode 100644 index cd8b8b4..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Protocols.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - Protocols Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Protocols

-

The following protocols are available globally.

- -
-
-
-
    -
  • -
    - - - - ProtobufOneOf - -
    -
    -
    -
    -
    -
    -

    Add conformance to this protocol to enums which should be encoded as Protobuf Oneof values.

    - -

    Conform a suitable enum to the ProtobufOneOf protocol to indicate that it should be encoded as single value.

    - -

    The enum must have exactly one associated value for each case, and the integer keys must not overlap with the ones defined for the enclosing struct.

    -
    struct Test: Codable {
    -
    -   // The oneof field
    -   let alternatives: OneOf
    -
    -   // The OneOf definition
    -   enum OneOf: Codable, ProtobufOneOf {
    -       case integer(Int)
    -       case string(String)
    -
    -       // Field values, must not overlap with `Test.CodingKeys`
    -       enum CodingKeys: Int, CodingKey {
    -           case integer = 1
    -           case string = 2
    -       }
    -   }
    -
    -    enum CodingKeys: Int, CodingKey {
    -       // The field id of the Oneof field is not used
    -        case alternatives = 123456
    -    }
    -}
    -
    - -

    See: Protocol Buffer Language Guide: Oneof and Swift Protobuf: Oneof Fields

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public protocol ProtobufOneOf
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - FixedSizeCompatible - -
    -
    -
    -
    -
    -
    -

    An integer type which can be forced to use a fixed-length encoding instead of variable-length encoding.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public protocol FixedSizeCompatible
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - SignedValueCompatible - -
    -
    -
    -
    -
    -
    -

    A signed integer which can be forced to use zig-zag encoding.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public protocol SignedValueCompatible
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Protocols/FixedSizeCompatible.html b/docs/docsets/.docset/Contents/Resources/Documents/Protocols/FixedSizeCompatible.html deleted file mode 100644 index c3204f1..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Protocols/FixedSizeCompatible.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - FixedSizeCompatible Protocol Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

FixedSizeCompatible

-
-
- -
public protocol FixedSizeCompatible
- -
-
-

An integer type which can be forced to use a fixed-length encoding instead of variable-length encoding.

- -
-
-
-
    -
  • -
    - - - - fixedSizeDataType - -
    -
    -
    -
    -
    -
    -

    The wire type of the type, which has a constant length

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var fixedSizeDataType: DataType { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedProtoType - -
    -
    -
    -
    -
    -
    -

    The protobuf type equivalent to the fixed size type

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    var fixedProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - fixedSizeEncoded - -
    -
    -
    -
    -
    -
    -

    The value encoded as fixed size binary data

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    var fixedSizeEncoded: Data { get }
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Decode the value from binary data.

    -
    -

    Throws

    - DecodingError - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    init(fromFixedSize data: Data, path: [CodingKey]) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - data - - -
    -

    The binary data of the correct size for the type.

    -
    -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Protocols/SignedValueCompatible.html b/docs/docsets/.docset/Contents/Resources/Documents/Protocols/SignedValueCompatible.html deleted file mode 100644 index dac9f05..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Protocols/SignedValueCompatible.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - SignedValueCompatible Protocol Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

SignedValueCompatible

-
-
- -
public protocol SignedValueCompatible
- -
-
-

A signed integer which can be forced to use zig-zag encoding.

- -
-
-
-
    -
  • -
    - - - - positiveProtoType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    var positiveProtoType: String { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Structs.html b/docs/docsets/.docset/Contents/Resources/Documents/Structs.html deleted file mode 100644 index 9b9d7dc..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Structs.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - Structures Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

Structures

-

The following structures are available globally.

- -
-
-
-
    -
  • -
    - - - - FixedSize - -
    -
    -
    -
    -
    -
    -

    A wrapper for integer values which ensures that values are encoded in binary format using a fixed size.

    - -

    Use the property wrapped within a Codable definition to enforce fixed-width encoding for a property:

    -
     struct MyStruct: Codable {
    -
    -     /// Always encoded as 4 bytes
    -     @FixedSize
    -     var largeInteger: Int32
    - }
    -
    - -

    The FixedSize property wrapper is supported for UInt32, UInt64, Int32, and Int64 types.

    - - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    @propertyWrapper
    -public struct FixedSize<WrappedValue>: ExpressibleByIntegerLiteral
    -where WrappedValue: FixedSizeCompatible,
    -      WrappedValue: FixedWidthInteger,
    -      WrappedValue: Codable
    -
    extension FixedSize: Equatable
    -
    extension FixedSize: Comparable
    -
    extension FixedSize: Hashable
    -
    extension FixedSize: Encodable
    -
    extension FixedSize: Decodable
    -
    extension FixedSize: CodablePrimitive, DataTypeProvider where WrappedValue: DataTypeProvider
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - SignedValue - -
    -
    -
    -
    -
    -
    -

    A wrapper for integers more efficient for negative values.

    - -

    This encoding format enforces Zig-Zag encoding, which is more efficient when numbers are often negative.

    -
    -

    Note

    - This wrapper is only useful when encoding and decoding to/from protobuf data. -It has no effect for the standard BinaryEncoder and BinaryDecoder, where integer values are -encoded using Zig-Zag encoding by default. - -
    - -

    Use the property wrapped within a Codable definition to enforce fixed-width encoding for a property:

    -
     struct MyStruct: Codable {
    -
    -     /// Efficient for small positive and negative values
    -     @SignedValue
    -     var count: Int32
    - }
    -
    - -

    The SignedValue property wrapper is supported for Int, Int32, and Int64 types.

    - - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    @propertyWrapper
    -public struct SignedValue<WrappedValue>: ExpressibleByIntegerLiteral
    -where WrappedValue: SignedValueCompatible,
    -      WrappedValue: SignedInteger,
    -      WrappedValue: FixedWidthInteger,
    -      WrappedValue: Codable
    -
    extension SignedValue: Equatable
    -
    extension SignedValue: Comparable
    -
    extension SignedValue: Hashable
    -
    extension SignedValue: Encodable
    -
    extension SignedValue: Decodable
    -
    extension SignedValue: CodablePrimitive, DataTypeProvider where WrappedValue: ZigZagCodable, WrappedValue: DataTypeProvider
    -
    extension SignedValue: ProtobufCodable where WrappedValue: ZigZagCodable, WrappedValue: SignedValueCompatible
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Structs/FixedSize.html b/docs/docsets/.docset/Contents/Resources/Documents/Structs/FixedSize.html deleted file mode 100644 index 0fe506e..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Structs/FixedSize.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - FixedSize Structure Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

FixedSize

-
-
- -
@propertyWrapper
-public struct FixedSize<WrappedValue>: ExpressibleByIntegerLiteral
-where WrappedValue: FixedSizeCompatible,
-      WrappedValue: FixedWidthInteger,
-      WrappedValue: Codable
-
extension FixedSize: Equatable
-
extension FixedSize: Comparable
-
extension FixedSize: Hashable
-
extension FixedSize: Encodable
-
extension FixedSize: Decodable
-
extension FixedSize: CodablePrimitive, DataTypeProvider where WrappedValue: DataTypeProvider
- -
-
-

A wrapper for integer values which ensures that values are encoded in binary format using a fixed size.

- -

Use the property wrapped within a Codable definition to enforce fixed-width encoding for a property:

-
 struct MyStruct: Codable {
-
-     /// Always encoded as 4 bytes
-     @FixedSize
-     var largeInteger: Int32
- }
-
- -

The FixedSize property wrapper is supported for UInt32, UInt64, Int32, and Int64 types.

- - -
-
-
-
    -
  • -
    - - - - IntegerLiteralType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public typealias IntegerLiteralType = WrappedValue.IntegerLiteralType
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - wrappedValue - -
    -
    -
    -
    -
    -
    -

    The value wrapped in the fixed-size container

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var wrappedValue: WrappedValue
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(wrappedValue:) - -
    -
    -
    -
    -
    -
    -

    Wrap an integer value in a fixed-size container

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(wrappedValue: WrappedValue)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - wrappedValue - - -
    -

    The integer to wrap

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(integerLiteral:) - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(integerLiteral value: WrappedValue.IntegerLiteralType)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - <(_:_:) - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func < (lhs: FixedSize<WrappedValue>, rhs: FixedSize<WrappedValue>) -> Bool
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(to:) - -
    -
    -
    -
    -
    -
    -

    Encode the wrapped value transparently to the given encoder.

    -
    -

    Throws

    - Errors from the decoder when attempting to encode a value in a single value container. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode(to encoder: Encoder) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - encoder - - -
    -

    The encoder to use for encoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(from:) - -
    -
    -
    -
    -
    -
    -

    Decode a wrapped value from a decoder.

    -
    -

    Throws

    - Errors from the decoder when reading a single value container or decoding the wrapped value from it. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(from decoder: Decoder) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - decoder - - -
    -

    The decoder to use for decoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - max - -
    -
    -
    -
    -
    -
    -

    The maximum representable integer in this type.

    - -

    For unsigned integer types, this value is (2 ** bitWidth) - 1, where -** is exponentiation. For signed integer types, this value is -(2 ** (bitWidth - 1)) - 1.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var max: `Self` { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - min - -
    -
    -
    -
    -
    -
    -

    The minimum representable integer in this type.

    - -

    For unsigned integer types, this value is always 0. For signed integer -types, this value is -(2 ** (bitWidth - 1)), where ** is -exponentiation.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var min: `Self` { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - zero - -
    -
    -
    -
    -
    -
    -

    The zero value.

    - -

    Zero is the identity element for addition. For any value, x + .zero == x and .zero + x == x.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var zero: `Self` { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Structs/SignedValue.html b/docs/docsets/.docset/Contents/Resources/Documents/Structs/SignedValue.html deleted file mode 100644 index 531d295..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Structs/SignedValue.html +++ /dev/null @@ -1,576 +0,0 @@ - - - - SignedValue Structure Reference - - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
-

SignedValue

-
-
- -
@propertyWrapper
-public struct SignedValue<WrappedValue>: ExpressibleByIntegerLiteral
-where WrappedValue: SignedValueCompatible,
-      WrappedValue: SignedInteger,
-      WrappedValue: FixedWidthInteger,
-      WrappedValue: Codable
-
extension SignedValue: Equatable
-
extension SignedValue: Comparable
-
extension SignedValue: Hashable
-
extension SignedValue: Encodable
-
extension SignedValue: Decodable
-
extension SignedValue: CodablePrimitive, DataTypeProvider where WrappedValue: ZigZagCodable, WrappedValue: DataTypeProvider
-
extension SignedValue: ProtobufCodable where WrappedValue: ZigZagCodable, WrappedValue: SignedValueCompatible
- -
-
-

A wrapper for integers more efficient for negative values.

- -

This encoding format enforces Zig-Zag encoding, which is more efficient when numbers are often negative.

-
-

Note

- This wrapper is only useful when encoding and decoding to/from protobuf data. -It has no effect for the standard BinaryEncoder and BinaryDecoder, where integer values are -encoded using Zig-Zag encoding by default. - -
- -

Use the property wrapped within a Codable definition to enforce fixed-width encoding for a property:

-
 struct MyStruct: Codable {
-
-     /// Efficient for small positive and negative values
-     @SignedValue
-     var count: Int32
- }
-
- -

The SignedValue property wrapper is supported for Int, Int32, and Int64 types.

- - -
-
-
-
    -
  • -
    - - - - IntegerLiteralType - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public typealias IntegerLiteralType = WrappedValue.IntegerLiteralType
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - wrappedValue - -
    -
    -
    -
    -
    -
    -

    The value wrapped in the fixed-size container

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var wrappedValue: WrappedValue
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(wrappedValue:) - -
    -
    -
    -
    -
    -
    -

    Wrap an integer value in a fixed-size container

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(wrappedValue: WrappedValue)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - wrappedValue - - -
    -

    The integer to wrap

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(integerLiteral:) - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(integerLiteral value: WrappedValue.IntegerLiteralType)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - <(_:_:) - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public static func < (lhs: SignedValue<WrappedValue>, rhs: SignedValue<WrappedValue>) -> Bool
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - encode(to:) - -
    -
    -
    -
    -
    -
    -

    Encode the wrapped value transparently to the given encoder.

    -
    -

    Throws

    - Errors from the decoder when attempting to encode a value in a single value container. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func encode(to encoder: Encoder) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - encoder - - -
    -

    The encoder to use for encoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(from:) - -
    -
    -
    -
    -
    -
    -

    Decode a wrapped value from a decoder.

    -
    -

    Throws

    - Errors from the decoder when reading a single value container or decoding the wrapped value from it. - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(from decoder: Decoder) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - decoder - - -
    -

    The decoder to use for decoding.

    -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - zero - -
    -
    -
    -
    -
    -
    -

    The zero value.

    - -

    Zero is the identity element for addition. For any value, x + .zero == x and .zero + x == x.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var zero: `Self` { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - max - -
    -
    -
    -
    -
    -
    -

    The maximum representable integer in this type.

    - -

    For unsigned integer types, this value is (2 ** bitWidth) - 1, where -** is exponentiation. For signed integer types, this value is -(2 ** (bitWidth - 1)) - 1.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var max: `Self` { get }
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - min - -
    -
    -
    -
    -
    -
    -

    The minimum representable integer in this type.

    - -

    For unsigned integer types, this value is always 0. For signed integer -types, this value is -(2 ** (bitWidth - 1)), where ** is -exponentiation.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    static var min: `Self` { get }
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/assets/logo.png b/docs/docsets/.docset/Contents/Resources/Documents/assets/logo.png deleted file mode 100644 index 9c6f100..0000000 Binary files a/docs/docsets/.docset/Contents/Resources/Documents/assets/logo.png and /dev/null differ diff --git a/docs/docsets/.docset/Contents/Resources/Documents/assets/platforms.svg b/docs/docsets/.docset/Contents/Resources/Documents/assets/platforms.svg deleted file mode 100644 index 405ea78..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/assets/platforms.svg +++ /dev/null @@ -1 +0,0 @@ -platformsplatformsiOS | macOS | Linux | tvOS | watchOSiOS | macOS | Linux | tvOS | watchOS \ No newline at end of file diff --git a/docs/docsets/.docset/Contents/Resources/Documents/assets/swift.svg b/docs/docsets/.docset/Contents/Resources/Documents/assets/swift.svg deleted file mode 100644 index c60e0cb..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/assets/swift.svg +++ /dev/null @@ -1 +0,0 @@ -SwiftSwift5.65.6 \ No newline at end of file diff --git a/docs/docsets/.docset/Contents/Resources/Documents/badge.svg b/docs/docsets/.docset/Contents/Resources/Documents/badge.svg deleted file mode 100644 index a096fec..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/badge.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - documentation - - - documentation - - - 100% - - - 100% - - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/binaryformat.html b/docs/docsets/.docset/Contents/Resources/Documents/binaryformat.html deleted file mode 100644 index e71697f..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/binaryformat.html +++ /dev/null @@ -1,505 +0,0 @@ - - - - BinaryFormat Reference - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
- -

Binary data structure

- -

Note: The BinaryCodable format is optimized for size, but does not go all-out to create the smallest binary sizes possible. -The binary format, while being efficient, needs to serve as a general-purpose encoding, which will never be as efficient than a custom format optimized for a very specific use case. -If this is your goal, then simply using Codable with it’s key-value approach will not be the best solution. An unkeyed format optimized for the actually encoded data will be more suitable. But if you’re really looking into this kind of efficiency, then you probably know this already.

- -

The encoding format used by BinaryCodable is similar to Google’s Protocol Buffers in some aspects, but provides much more flexibility regarding the different types which can be encoded, including the ability to encode Optional, Set, single values, multidimensional Arrays, and more.

-

Integer encoding

- -

Integers are encoded with different strategies depending on their size. Smaller types, like UInt8, Int8, UInt16, and Int16 are encoded using their binary representations in little-endian format.

- -

Larger integers, like UInt32, Int32, UInt64, Int64, Int, and UInt are (by default) encoded using variable length zig-zag encoding, similar to Protobuf signed integers. This means that smaller values are encoded as shorter binary representations, which is useful if integer values are mostly small. -Note: The Varint implementation is not equivalent to Protobuf, since BinaryCodable uses the last byte of a large integer directly, and thus encodes Int.max with 9 Byte instead of 10. This encoding is adapted when enforcing protobuf compatibility.

- -

The property wrapper FixedSize forces the values to be encoded using their little-endian representations.

-

Floating-point types

- -

Float and Double values are encoded using their binary representations in little-endian format.

-

Strings

- -

Swift String values are encoded using their UTF-8 representations. If a string can’t be encoded this way, then encoding fails.

-

Booleans

- -

Bool values are always encoded as a single byte, using 1 for true, and 0 for false.

-

Arrays

- -

Arrays (and other sequences) are encoded by converting each item to binary data, and concatenating the results. Elements with variable length (like String) are prepended with their length encoded as a Varint.

-

Arrays of Optionals

- -

It is possible to encode arrays where the elements are Optional, e.g. [Bool?]. -For all types with compiler-generated Codable conformance, an optional is prepended with one byte (1 for the .some() case, 0 for nil). -If the optional has a value, then the encoded data follows the 1 byte. -If nil is encoded, then no additional data is added. -This means that an array of [Bool?] with the values [true, nil, false] is encoded as [1, 1, 0, 1, 0].

-
-

Note

- One benefit of this encoding is that top-level sequences can be joined using their binary data, where encoded([a,b]) | encoded([c,d]) == encoded([a,b,c,d]). - -
- -

Custom implementations of Encodable and Decodable can directly call encodeNil() on UnkeyedEncodingContainer and decodeNil() on UnkeyedDecodingContainer. -This feature is not supported in the standard configuration and will result in a fatal error. -If these functions are needed, then the prependNilIndexSetForUnkeyedContainers must be set for the encoder and decoder. -If this option is set to true, then each unkeyed container is prepended with a “nil index set”. -It first consists of the number of nil elements in the sequence (only those encoded using encodeNil()), encoded as a Varint. -Then follow the indices in the array where nil values are present, each encoded as a Varint. -The decoder can then first parse this nil set, and return the appropriate value for each position where a nil value is encoded when decodeNil() is called. -This approach is fairly efficient while only few nil values are encoded, or while the sequence doesn’t contain a large number of elements. -For arrays that don’t contain optionals, only a single byte (0) is prepended to the binary representation, to signal that there are no nil indices in the sequence.

- -

More efficient ways could be devised to handle arrays of optionals, like specifying the number of nil or non-nil elements following one another, but the increased encoding and decoding complexity don’t justify these gains in communication efficiency.

-

Structs

- -

Structs are encoded using Codable‘s KeyedEncodingContainer, which uses String or Int coding keys to distinguish the properties of the types. -By default, Swift uses the property names as String keys, which are used to encode each property as a key-value pair on the wire. -The first value is a Varint, which contains the length of the string key, plus additional information about the data associated with the key. -The bits 0-2 are used to signal the size value, and bit 3 of the Varint indicates whether the key is a string key (1 = string, 0 = int). -The following data types are possible:

- -

| Data type | Raw value | Protobuf | Swift types | Description | -| :———————- | :——– | :—————- | :————————————————————————————- | -| variableLengthInteger | 0 | varint/zigzag | Int, Int32, Int64, UInt, UInt32, UInt64 | A Base128 Varint using 1-9 bytes of data | -| eightBytes | 1 | fixed64bit | Double, FixedSize<Int64>, FixedSize<Int>, FixedSize<UInt64>, FixedSize<UInt> | A 64-bit float or integer in little-endian encoding. | -| variableLength | 2 | delimited | String, Struct, … | The length of the data encoded as a Varint followed by length bytes | -| fourBytes | 5 | fixed32bit | Float, FixedSize<Int32>, FixedSize<UInt32> | A 32-bit float or integer in little-endian encoding. | -| byte | 6 | - | Bool, UInt8, Int8 | A single byte storing a number or boolean | -| twoBytes | 7 | - | Int16, UInt16 | Two bytes storing an integer using little-endian format |

- -

With the four lower bits occupied by the data type and the string key indicator, the remaining bits are left to encode the length of the string key.

- -

For example, a property named xyz of type UInt8 with value 123 would be encoded to the following:

- - - - - - - - - - - - - - - - - - - - - - - - -
Byte 0Byte 1Byte 2Byte 3Byte 4
0 011 1 11001111000011110010111101001111011
Length 3, String key, Data type bytexyz123
-

Integer keys

- -

The Swift Codable framework also provides the ability to specify integer keys for properties, which can significantly reduce the binary size. Integer keys can be assigned to properties by implementing custom CodingKeys for a type:

-
struct MyCodable: Codable {
-
-    let xyz: UInt8
-
-    enum CodingKeys: Int, CodingKey {
-        case xyz = 2
-    }
-}
-
- -

Integer coding keys are encoded as Varint instead of the String key length. This results in the following encoding for the same example as before:

- - - - - - - - - - - - - - - -
Byte 0Byte 1
0 010 0 11001111011
Integer key 2, Int key, Data type byte123
- -

Evidently this is a significant improvement, especially for long property names. Note that while it is possible to specify any integer as the key (between 2^59 and -2^59), small, positive integers are the most efficient.

-

Optional properties

- -

Any properties of structs or other keyed containers is omitted from the binary data, i.e. nothing is encoded. -The absence of a key then indicates to the decoder that the value is nil. -For multiple optionals (e.g. Bool??), the inner optional is encoded as a varLen type, with the same encoding as optionals in arrays.

-

Codable quirks

- -

There are some interesting details on how Codable treats certain types, which produce unexpected binary data. Although BinaryCodable decodes all these cases correctly, implementing these “features” may be difficult on other platforms.

-

Enums with associated values

- -

Given an enum with associated values:

-
enum MyEnum: Codable {
-    case one(String)
-    case two(Bool, Data)
-}
-
- -

The encoding will consist of:

- -
    -
  • The enum case name as a String key
  • -
  • A keyed container with: - -
      -
    • All associated values in the order of their definition, keyed by "_0", "_1", …
    • -
  • -
- -

For example, the value

-
let value = MyEnum.one("Some")
-
- -

would be encoded as:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Byte 0Byte 1 - 3Byte 4Byte 5Byte 6 - 7Byte 8Byte 9 - 12
0x3A0x6F 0x6E 0x650x080x2A0x5F 0x300x040x53 0x6E 0x64 0x08
String key (Len 3), VarLenoneLength 8String key (Len 2)_0Length 4Some
- -

Let’s use the same example with integer keys:

-
enum MyEnum: Codable {
-    case one(String)
-    case two(Bool, UInt8)
-
-    enum CodingKeys: Int, CodingKey {
-        case one = 1
-        case two = 2
-    }
-}
-
- -

Then, the value

-
let value = MyEnum.two(true, 123)
-
- -

would be encoded as:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Byte 0Byte 1Byte 2Byte 3 - 4Byte 5Byte 6Byte 7 - 8Byte 9
0x220x080x2E0x5F 0x300x010x2E0x5F 0x310x7B
Int key (2), VarLenLength 8String key (Len 2), Byte_0Bool(true)String key (Len 2), Byte_1UInt8(123)
- -

Note: Since the associated values are encoded in a keyed container, there order in the binary data may be different, unless the sortKeysDuringEncoding option is set to true.

-

Special dictionaries

- -

Most dictionaries are treated as Unkeyed Containers by Codable, and each key value pair is encoded by first encoding the key, followed by the value, thus creating the flat structure:

- - - - - - - - - - - -
Key 1Value 1Key 2Value 2Key 3Value 3
-

Dictionaries with Integer keys

- -

For all dictionaries, which use Int as the key, e.g. [Int: String], [Int: Int], or generally [Int: ...], the encoding is done using a Keyed container, where each dictionary value is encoded using a CodingKey with an integer value. This results in a structure more resembling struct encoding with integer keys:

- - - - - - - - - - - - - - - - - - - -
Byte(s)Byte(s)Byte(s)Byte(s)Byte(s)Byte(s)
Int Key(Key 1), Data typeValue 1Int Key(Key 2), Data typeValue 2Int Key(Key 3), Data typeValue 3
- -

For example, the following works:

-
struct MyStruct: Codable {
-    let a: Int
-    let b: Int
-    let c: Int
-}
-
-// Encode a dictionary
-let input: [String: Int] = ["a" : 123, "b": 0, "c": -123456]
-let data = try BinaryEncoder.encode(input)
-
-// Decode as struct
-let decoded = try BinaryDecoder.decode(MyStruct.self, from: data)
-
- -

It also works the other way round:

-
// Encode struct
-let input = MyStruct(a: 123, b: 0, c: -123456)
-let data = try BinaryEncoder.encode(input)
-
-// Decode as dictionary
-let decoded: [String: Int] = try BinaryDecoder.decode(from: data)
-
- -

Note that this only works for dictionaries with concrete Encodable values, e.g. [String: Encodable] won’t work.

-

Dictionaries with String keys

- -

For dictionaries with String keys ([String: ...]), the process is similar to the above, except with CodingKeys having the stringValue of the key. There is another weird exception though: Whenever a String can be represented by an integer (i.e. when String(key) != nil), then the corresponding CodingKey will have its integerValue also set. This means that for dictionaries with integer keys, there may be a mixture of integer and string keys present in the binary data, depending on the input values. But don’t worry, BinaryCodable will also handle these cases correctly.

-

Stream encoding

- -

The encoding for data streams is only differs from standard encoding in two key aspects.

-

Added length information

- -

Each top-level element is encoded as if it is part of an unkeyed container (which it essentially is), meaning that each element has the necessary length information prepended to determine it’s size. -Only types with data type variable length have their length prepended using varint encoding. -This concerns String and Data, as well as complex types like structs and arrays, among others.

-

Optionals

- -

A single byte is prepended to each Optional element, where binary 0x01 is used to indicate a non-optional value, and 0x00 is used to signal an optional value. -nil values have no additional data, so each is encoded using one byte.

- -
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/css/highlight.css b/docs/docsets/.docset/Contents/Resources/Documents/css/highlight.css deleted file mode 100644 index c170357..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/css/highlight.css +++ /dev/null @@ -1,202 +0,0 @@ -/*! Jazzy - https://github.com/realm/jazzy - * Copyright Realm Inc. - * SPDX-License-Identifier: MIT - */ -/* Credit to https://gist.github.com/wataru420/2048287 */ -.highlight .c { - color: #999988; - font-style: italic; } - -.highlight .err { - color: #a61717; - background-color: #e3d2d2; } - -.highlight .k { - color: #000000; - font-weight: bold; } - -.highlight .o { - color: #000000; - font-weight: bold; } - -.highlight .cm { - color: #999988; - font-style: italic; } - -.highlight .cp { - color: #999999; - font-weight: bold; } - -.highlight .c1 { - color: #999988; - font-style: italic; } - -.highlight .cs { - color: #999999; - font-weight: bold; - font-style: italic; } - -.highlight .gd { - color: #000000; - background-color: #ffdddd; } - -.highlight .gd .x { - color: #000000; - background-color: #ffaaaa; } - -.highlight .ge { - color: #000000; - font-style: italic; } - -.highlight .gr { - color: #aa0000; } - -.highlight .gh { - color: #999999; } - -.highlight .gi { - color: #000000; - background-color: #ddffdd; } - -.highlight .gi .x { - color: #000000; - background-color: #aaffaa; } - -.highlight .go { - color: #888888; } - -.highlight .gp { - color: #555555; } - -.highlight .gs { - font-weight: bold; } - -.highlight .gu { - color: #aaaaaa; } - -.highlight .gt { - color: #aa0000; } - -.highlight .kc { - color: #000000; - font-weight: bold; } - -.highlight .kd { - color: #000000; - font-weight: bold; } - -.highlight .kp { - color: #000000; - font-weight: bold; } - -.highlight .kr { - color: #000000; - font-weight: bold; } - -.highlight .kt { - color: #445588; } - -.highlight .m { - color: #009999; } - -.highlight .s { - color: #d14; } - -.highlight .na { - color: #008080; } - -.highlight .nb { - color: #0086B3; } - -.highlight .nc { - color: #445588; - font-weight: bold; } - -.highlight .no { - color: #008080; } - -.highlight .ni { - color: #800080; } - -.highlight .ne { - color: #990000; - font-weight: bold; } - -.highlight .nf { - color: #990000; } - -.highlight .nn { - color: #555555; } - -.highlight .nt { - color: #000080; } - -.highlight .nv { - color: #008080; } - -.highlight .ow { - color: #000000; - font-weight: bold; } - -.highlight .w { - color: #bbbbbb; } - -.highlight .mf { - color: #009999; } - -.highlight .mh { - color: #009999; } - -.highlight .mi { - color: #009999; } - -.highlight .mo { - color: #009999; } - -.highlight .sb { - color: #d14; } - -.highlight .sc { - color: #d14; } - -.highlight .sd { - color: #d14; } - -.highlight .s2 { - color: #d14; } - -.highlight .se { - color: #d14; } - -.highlight .sh { - color: #d14; } - -.highlight .si { - color: #d14; } - -.highlight .sx { - color: #d14; } - -.highlight .sr { - color: #009926; } - -.highlight .s1 { - color: #d14; } - -.highlight .ss { - color: #990073; } - -.highlight .bp { - color: #999999; } - -.highlight .vc { - color: #008080; } - -.highlight .vg { - color: #008080; } - -.highlight .vi { - color: #008080; } - -.highlight .il { - color: #009999; } diff --git a/docs/docsets/.docset/Contents/Resources/Documents/css/jazzy.css b/docs/docsets/.docset/Contents/Resources/Documents/css/jazzy.css deleted file mode 100644 index 2e38713..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/css/jazzy.css +++ /dev/null @@ -1,439 +0,0 @@ -/*! Jazzy - https://github.com/realm/jazzy - * Copyright Realm Inc. - * SPDX-License-Identifier: MIT - */ -html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { - background: transparent; - border: 0; - margin: 0; - outline: 0; - padding: 0; - vertical-align: baseline; } - -body { - background-color: #f2f2f2; - font-family: Helvetica, freesans, Arial, sans-serif; - font-size: 14px; - -webkit-font-smoothing: subpixel-antialiased; - word-wrap: break-word; } - -h1, h2, h3 { - margin-top: 0.8em; - margin-bottom: 0.3em; - font-weight: 100; - color: black; } - -h1 { - font-size: 2.5em; } - -h2 { - font-size: 2em; - border-bottom: 1px solid #e2e2e2; } - -h4 { - font-size: 13px; - line-height: 1.5; - margin-top: 21px; } - -h5 { - font-size: 1.1em; } - -h6 { - font-size: 1.1em; - color: #777; } - -.section-name { - color: gray; - display: block; - font-family: Helvetica; - font-size: 22px; - font-weight: 100; - margin-bottom: 15px; } - -pre, code { - font: 0.95em Menlo, monospace; - color: #777; - word-wrap: normal; } - -p code, li code { - background-color: #eee; - padding: 2px 4px; - border-radius: 4px; } - -pre > code { - padding: 0; } - -a { - color: #0088cc; - text-decoration: none; } - a code { - color: inherit; } - -ul { - padding-left: 15px; } - -li { - line-height: 1.8em; } - -img { - max-width: 100%; } - -blockquote { - margin-left: 0; - padding: 0 10px; - border-left: 4px solid #ccc; } - -hr { - height: 1px; - border: none; - background-color: #e2e2e2; } - -.footnote-ref { - display: inline-block; - scroll-margin-top: 70px; } - -.footnote-def { - scroll-margin-top: 70px; } - -.content-wrapper { - margin: 0 auto; - width: 980px; } - -header { - font-size: 0.85em; - line-height: 32px; - background-color: #414141; - position: fixed; - width: 100%; - z-index: 3; } - header img { - padding-right: 6px; - vertical-align: -3px; - height: 16px; } - header a { - color: #fff; } - header p { - float: left; - color: #999; } - header .header-right { - float: right; - margin-left: 16px; } - -#breadcrumbs { - background-color: #f2f2f2; - height: 21px; - padding-top: 17px; - position: fixed; - width: 100%; - z-index: 2; - margin-top: 32px; } - #breadcrumbs #carat { - height: 10px; - margin: 0 5px; } - -.sidebar { - background-color: #f9f9f9; - border: 1px solid #e2e2e2; - overflow-y: auto; - overflow-x: hidden; - position: fixed; - top: 70px; - bottom: 0; - width: 230px; - word-wrap: normal; } - -.nav-groups { - list-style-type: none; - background: #fff; - padding-left: 0; } - -.nav-group-name { - border-bottom: 1px solid #e2e2e2; - font-size: 1.1em; - font-weight: 100; - padding: 15px 0 15px 20px; } - .nav-group-name > a { - color: #333; } - -.nav-group-tasks { - margin-top: 5px; } - -.nav-group-task { - font-size: 0.9em; - list-style-type: none; - white-space: nowrap; } - .nav-group-task a { - color: #888; } - -.main-content { - background-color: #fff; - border: 1px solid #e2e2e2; - margin-left: 246px; - position: absolute; - overflow: hidden; - padding-bottom: 20px; - top: 70px; - width: 734px; } - .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { - margin-bottom: 1em; } - .main-content p { - line-height: 1.8em; } - .main-content section .section:first-child { - margin-top: 0; - padding-top: 0; } - .main-content section .task-group-section .task-group:first-of-type { - padding-top: 10px; } - .main-content section .task-group-section .task-group:first-of-type .section-name { - padding-top: 15px; } - .main-content section .heading:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - .main-content .section-name p { - margin-bottom: inherit; - line-height: inherit; } - .main-content .section-name code { - background-color: inherit; - padding: inherit; - color: inherit; } - -.section { - padding: 0 25px; } - -.highlight { - background-color: #eee; - padding: 10px 12px; - border: 1px solid #e2e2e2; - border-radius: 4px; - overflow-x: auto; } - -.declaration .highlight { - overflow-x: initial; - padding: 0 40px 40px 0; - margin-bottom: -25px; - background-color: transparent; - border: none; } - -.section-name { - margin: 0; - margin-left: 18px; } - -.task-group-section { - margin-top: 10px; - padding-left: 6px; - border-top: 1px solid #e2e2e2; } - -.task-group { - padding-top: 0px; } - -.task-name-container a[name]:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - -.section-name-container { - position: relative; - display: inline-block; } - .section-name-container .section-name-link { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - margin-bottom: 0; } - .section-name-container .section-name { - position: relative; - pointer-events: none; - z-index: 1; } - .section-name-container .section-name a { - pointer-events: auto; } - -.item { - padding-top: 8px; - width: 100%; - list-style-type: none; } - .item a[name]:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - .item code { - background-color: transparent; - padding: 0; } - .item .token, .item .direct-link { - display: inline-block; - text-indent: -20px; - padding-left: 3px; - margin-left: 35px; - font-size: 11.9px; - transition: all 300ms; } - .item .token-open { - margin-left: 20px; } - .item .discouraged { - text-decoration: line-through; } - .item .declaration-note { - font-size: .85em; - color: gray; - font-style: italic; } - -.pointer-container { - border-bottom: 1px solid #e2e2e2; - left: -23px; - padding-bottom: 13px; - position: relative; - width: 110%; } - -.pointer { - background: #f9f9f9; - border-left: 1px solid #e2e2e2; - border-top: 1px solid #e2e2e2; - height: 12px; - left: 21px; - top: -7px; - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); - position: absolute; - width: 12px; } - -.height-container { - display: none; - left: -25px; - padding: 0 25px; - position: relative; - width: 100%; - overflow: hidden; } - .height-container .section { - background: #f9f9f9; - border-bottom: 1px solid #e2e2e2; - left: -25px; - position: relative; - width: 100%; - padding-top: 10px; - padding-bottom: 5px; } - -.aside, .language { - padding: 6px 12px; - margin: 12px 0; - border-left: 5px solid #dddddd; - overflow-y: hidden; } - .aside .aside-title, .language .aside-title { - font-size: 9px; - letter-spacing: 2px; - text-transform: uppercase; - padding-bottom: 0; - margin: 0; - color: #aaa; - -webkit-user-select: none; } - .aside p:last-child, .language p:last-child { - margin-bottom: 0; } - -.language { - border-left: 5px solid #cde9f4; } - .language .aside-title { - color: #4b8afb; } - -.aside-warning, .aside-deprecated, .aside-unavailable { - border-left: 5px solid #ff6666; } - .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title { - color: #ff0000; } - -.graybox { - border-collapse: collapse; - width: 100%; } - .graybox p { - margin: 0; - word-break: break-word; - min-width: 50px; } - .graybox td { - border: 1px solid #e2e2e2; - padding: 5px 25px 5px 10px; - vertical-align: middle; } - .graybox tr td:first-of-type { - text-align: right; - padding: 7px; - vertical-align: top; - word-break: normal; - width: 40px; } - -.slightly-smaller { - font-size: 0.9em; } - -#footer { - position: relative; - top: 10px; - bottom: 0px; - margin-left: 25px; } - #footer p { - margin: 0; - color: #aaa; - font-size: 0.8em; } - -html.dash header, html.dash #breadcrumbs, html.dash .sidebar { - display: none; } - -html.dash .main-content { - width: 980px; - margin-left: 0; - border: none; - width: 100%; - top: 0; - padding-bottom: 0; } - -html.dash .height-container { - display: block; } - -html.dash .item .token { - margin-left: 0; } - -html.dash .content-wrapper { - width: auto; } - -html.dash #footer { - position: static; } - -form[role=search] { - float: right; } - form[role=search] input { - font: Helvetica, freesans, Arial, sans-serif; - margin-top: 6px; - font-size: 13px; - line-height: 20px; - padding: 0px 10px; - border: none; - border-radius: 1em; } - .loading form[role=search] input { - background: white url(../img/spinner.gif) center right 4px no-repeat; } - form[role=search] .tt-menu { - margin: 0; - min-width: 300px; - background: #fff; - color: #333; - border: 1px solid #e2e2e2; - z-index: 4; } - form[role=search] .tt-highlight { - font-weight: bold; } - form[role=search] .tt-suggestion { - font: Helvetica, freesans, Arial, sans-serif; - font-size: 14px; - padding: 0 8px; } - form[role=search] .tt-suggestion span { - display: table-cell; - white-space: nowrap; } - form[role=search] .tt-suggestion .doc-parent-name { - width: 100%; - text-align: right; - font-weight: normal; - font-size: 0.9em; - padding-left: 16px; } - form[role=search] .tt-suggestion:hover, - form[role=search] .tt-suggestion.tt-cursor { - cursor: pointer; - background-color: #4183c4; - color: #fff; } - form[role=search] .tt-suggestion:hover .doc-parent-name, - form[role=search] .tt-suggestion.tt-cursor .doc-parent-name { - color: #fff; } diff --git a/docs/docsets/.docset/Contents/Resources/Documents/docs/badge.svg b/docs/docsets/.docset/Contents/Resources/Documents/docs/badge.svg deleted file mode 100644 index a096fec..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/docs/badge.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - documentation - - - documentation - - - 100% - - - 100% - - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png b/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png deleted file mode 100755 index 29d2f7f..0000000 Binary files a/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png and /dev/null differ diff --git a/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png b/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png deleted file mode 100755 index 6f694c7..0000000 Binary files a/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png and /dev/null differ diff --git a/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png b/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png deleted file mode 100755 index 628da97..0000000 Binary files a/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png and /dev/null differ diff --git a/docs/docsets/.docset/Contents/Resources/Documents/img/spinner.gif b/docs/docsets/.docset/Contents/Resources/Documents/img/spinner.gif deleted file mode 100644 index e3038d0..0000000 Binary files a/docs/docsets/.docset/Contents/Resources/Documents/img/spinner.gif and /dev/null differ diff --git a/docs/docsets/.docset/Contents/Resources/Documents/index.html b/docs/docsets/.docset/Contents/Resources/Documents/index.html deleted file mode 100644 index bb6e157..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/index.html +++ /dev/null @@ -1,445 +0,0 @@ - - - - Reference - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
- -

- BinaryCodable -

- -

- - - - - - - - -

- -

This package provides convenient encoding and decoding to/from binary data for all Swift Codable types. It also provides limited cross-compatibility to Google Protocol Buffers.

-

Use cases

- -

There are only few encoders and decoders available for Swift’s Codable format, and Apple provides a JSONEncoder and a PropertyListEncoder for basic encoding. While these can cover some use cases (especially when interacting with Web Content through JSON), they lack encoding efficiency when designing APIs within an ecosystem. JSON, for example, is notoriously inefficient when it comes to binary data.

- -

One very popular alternative for binary data are Google’s Protocol Buffers, which offer broad support across different platforms and programming languages. But they don’t support Swift’s Codable protocol, and thus require manual message definitions, the Protobuf compiler, and a lot of copying between data structures during encoding and decoding.

- -

So if you’re looking for a decently efficient binary encoder in a pure Swift project, then BinaryCodable may be right for you. Simply make your structs (or classes!) conform to Codable, and BinaryCodable does the rest!

- -

The message format is similar to that of Protocol Buffers (with some additions to support more types). It is possible to create limited compatibility between the two formats to exchange data with systems that don’t support Swift.

-

Alternatives

-

Protocol Buffers

- -

Already mentioned above

-

CBORCoding

- -

If you’re looking for a Codable-compatible alternative which is also available on other platforms, with a well-defined spec. It appears to have nearly the same encoding efficiency as BinaryCodable.

-

PotentCodables

- -

Also offers CBOR encoding, plus a bunch of other things related to Codable.

-

Swift BSON

- -

Encoding according to the BSON specification. Less efficient binary represenation than Protocol Buffers and BinaryCodable, but mature. Used for MongoDB.

-

Installation

-

Swift Package Manager

- -

Simply include in your Package.swift:

-
dependencies: [
-    .package(
-        name: "BinaryCodable", 
-        url: "https://github.com/christophhagen/BinaryCodable", 
-        from: "1.0.0")
-],
-targets: [
-    .target(name: "MyTarget", dependencies: [
-        .product(name: "BinaryCodable", package: "BinaryCodable")
-    ])
-]
-
-

Xcode project

- -

Select your Project, navigate to the Package Dependencies tab, and add https://github.com/christophhagen/BinaryCodable using the + button.

-

Usage

- -

Let’s assume a message definition:

-
struct Message: Codable {
-
-    var sender: String
-
-    var isRead: Bool
-
-    var unreadCount: Int
-}
-
- -

Simply import the module where you need to encode or decode a message:

-
import BinaryCodable
-
-

Encoding

- -

Construct an encoder when converting instances to binary data, and feed the message(s) into it:

-
let message: Message = ...
-
-let encoder = BinaryEncoder()
-let data = try encoder.encode(message)
-
- -

It’s also possible to encode single values, arrays, optionals, sets, enums, dictionaries, and more, so long as they conform to Codable.

-

Decoding

- -

Decoding instances from binary data works much the same way:

-
let decoder = BinaryDecoder()
-let message = try decoder.decode(Message.self, from: data)
-
- -

Alternatively, the type can be inferred:

-
let message: Message = try decoder.decode(from: data)
-
-

Errors

- -

It is possible for both encoding and decoding to fail. -All possible errors occuring during encoding produce EncodingError errors, while unsuccessful decoding produces DecodingErrors. -Both are the default Errors provided by Swift, supplied with information describing the nature of the error. -See the documentation of the types to learn more about the different error conditions.

-

Unsupported features

- -

It is currently not supported to call func encodeNil() on SingleValueEncodingContainer for custom implementations of func encode(to:). -Future versions may include a special setting to enforce compatibility with this option.

-

Handling corrupted data

- -

The binary format provides no provisions to detect data corruption, and various errors can occur as the result of added, changed, or missing bytes and bits. -Additional external measures (checksums, error-correcting codes, …) should be applied if there is an increased risk of data corruption.

- -

As an example, consider the simple encoding of a String inside a struct, which consists of a key followed by the length of the string in bytes, and the string content. -The length of the string is encoded using variable-length encoding, so a single bit flip (in the MSB of the length byte) could result in a very large length being decoded, causing the decoder to wait for a very large number of bytes to decode the string. -This simple error would cause much data to be skipped, potentially corrupting the data stream indefinitely. -At the same time, it is not possible to determine with certainty where the error occured, making error recovery difficult without additional information about boundaries between elements.

- -

The decoding errors provided by the library are therefore only hints about error likely occuring from non-conformance to the binary format or version incompatibility, which are not necessarily the true causes of the failures when data corruption is present.

-

Coding Keys

- -

The Codable protocol uses CodingKey definitions to identify properties of instances. By default, coding keys are generated using the string values of the property names.

- -

Similar to JSON encoding, BinaryCodable can embed the property names in the encoded data.

- -

Unlike JSON (which is human-readable), the binary representation produced by BinaryCodable is intended for cases when efficient encoding is important. Codable allows the use of integer keys for each property, which significantly increases encoding efficiency. You can specify integer keys by adding an Int enum conforming to the CodingKey protocol to the Codable type:

-
struct Message: Codable {
-
-    var sender: String
-
-    var isRead: Bool
-
-    var unreadCount: Int
-
-    // Assign an integer to each property
-    enum CodingKeys: Int, CodingKey {
-        case sender = 1
-        case isRead = 2
-        case unreadCount = 3
-    }
-}
-
- -

The enum must have a raw value of either Int or String, and the cases must match the property names within the type (it is possible to omit keys for properties which should not be encoded).

- -

Using integer keys can significantly decrease the binary size, especially for long property names. Additionally, integer keys can be useful when intending to store the binary data persistently. Changes to property names can be performed in the code without breaking the decoding of older data (although this can also be achieved with custom String keys).

- -

Notes:

- -
    -
  • Small, positive integer keys produce the smallest binary sizes.
  • -
  • The 0 integer key shouldn’t be used, since it is also used internally when encoding super.
  • -
  • Negative values for integer keys are not recommended (but possible). Since the keys are encoded as Varint, they are very inefficient for negative numbers.
  • -
  • The allowed range for integer keys is from -576460752303423488 (-2^59, inclusive) to 576460752303423487 (2^59-1, inclusive). Values outside of these bounds will cause a fatalError crash.
  • -
-

Property wrappers

-

Fixed size integers

- -

While varints are efficient for small numbers, their encoding introduces a storage and computation penalty when the integers are often large, e.g. for random numbers. BinaryCodable provides the FixedSize wrapper, which forces integers to be encoded using their little-endian binary representations. This means that e.g. an Int32 is always encoded as 4 byte (instead of 1-5 bytes using Varint encoding). This makes 32-bit FixedSize types more efficient than Varint if values are often larger than 2^28 (2^56 for 64-bit types).

- -

Use the property wrapper within a Codable definition to enforce fixed-width encoding for a property:

-
 struct MyStruct: Codable {
-
-     /// Always encoded as 4 bytes
-     @FixedSize 
-     var largeInteger: Int32
- }
-
- -

The FixedSize wrapper is available to all Varint types: Int, UInt, Int32, UInt32, Int64, and UInt64.

- -

#### Other property wrappers

- -

There is an additional SignedValue wrapper, which is only useful when encoding in protobuf-compatible format.

-

Options

-

Sorting keys

- -

The BinaryEncoder provides the sortKeysDuringEncoding option, which forces fields in “keyed” containers, such as struct properties (and some dictionaries), to be sorted in the binary data. This sorting is done by using either the integer keys (if defined), or the property names. Dictionaries with Int or String keys are also sorted.

- -

Sorting the binary data does not influence decoding, but introduces a computation penalty during encoding. It should therefore only be used if the binary data must be consistent across multiple invocations.

- -

Note: The sortKeysDuringEncoding option does not neccessarily guarantee deterministic binary data, and should be used with care.

-

Encoding optionals in arrays

- -

Sequences of Optional values (like arrays, sets, …) are normally encoded one additional byte to indicate following value (0x01) or a nil value (0x00). -This works well for all compiler-generated conformances to Codable. -For custom implementations of func encode(to: Encoder) and init(from: Decoder), the encodeNil() is not supported on UnkeyedEncodingContainers. -If you must use this option, then it’s necessary to enable the prependNilIndexSetForUnkeyedContainers option on both BinaryEncoder and BinaryDecoder. -Optional values are then encoded using a nil index set. -The index of each nil element in the sequence is recorded, and only non-nil values are encoded. -The indices of nil elements are then prepended to the data as an array of integers. -During decoding, this index set is checked to place nil values between the non-nil elements at the appropriate indices.

-

Stream encoding and decoding

- -

The library provides the option to perform encoding and decoding of continuous streams, such as when writing sequences of elements to a file, or when transmitting data over a network. -This functionality can be used through BinaryStreamEncoder and BinaryStreamDecoder, causing the encoder to embed additional information into the data to allow continuous decoding (mostly length information). -Encoding and decoding is always done with sequences of one specific type, since multiple types in one stream could not be distinguished from one another.

- -

Encoding of a stream works similarly to normal encoding:

-
let encoder = BinaryStreamEncoder<Int>()
-let chunk1 = try encoder.encode(1)
-let chunk2 = try encoder.encode(contentsOf: [2,3])
-...
-
-let data = chunk1 + chunk2 + ...
-
- -

Decoding of the individual chunks, with the decoder returning all elements which can be decoded using the currently available data.

-
let decoder = BinaryStreamDecoder<Int>()
-let decoded1 = try decoder.decode(chunk1)
-print(decoded1) // [1]
-
-let decoded2 = try decoder.decode(chunk2)
-print(decoded2) // [2,3]
-
- -

The decoder has an internal buffer, so incomplete data can be inserted into the decoder as it becomes available. The output of decode(_ data:) will be empty until the next complete element is processed.

-

File encoding and decoding

- -

Writing data streams to files is a common use case, so the library also provides wrappers around BinaryStreamEncoder and BinaryStreamDecoder to perform these tasks. -The BinaryFileEncoder can be used to sequentially write elements to a file:

-
let encoder = BinaryFileEncoder<DataElement>(fileAt: url)
-try encoder.write(element1)
-try encoder.write(element2)
-...
-try encoder.close() // Close the file
-
- -

Elements will always be appended to the end of file, so existing files can be updated with additional data.

- -

Decoding works in a similar way, except with a callback to handle each element as it is decoded:

-
let decoder = BinaryFileDecoder<DataElement>(fileAt: url)
-try decoder.read { element in
-    // Process each element
-}
-
- -

There is also the possibility to read all elements at once using readAll(), or to read only one element at a time (readElement()).

-

Protocol Buffer compatibility

- -

Achieving Protocol Buffer compatibility is described in ProtobufSupport.md.

-

Binary format

- -

To learn more about the encoding format, see BinaryFormat.md.

-

Tests

- -

The library comes with an extensive test suite, which checks that encoding works correctly for many cases. These tests can be executed using swift test from the package root, or when opening the package using Xcode.

-

License

- -

MIT. See License.md

-

Roadmap

-

Generate protobuf definitions

- -

It should be possible to generate a string containing a working Protobuf definition for any type that is determined to be Protobuf compatible.

-

Speed

- -

Increasing the speed of the encoding and decoding process is not a huge priority at the moment. -If you have any pointers on how to improve the performance further, feel free to contribute.

-

Contributing

- -

Users of the library are encouraged to contribute to this repository.

-

Feature suggestions

- -

Please file an issue with a description of the feature you’re missing. Check other open and closed issues for similar suggestions and comment on them before creating a new issue.

-

Bug reporting

- -

File an issue with a clear description of the problem. Please include message definitions and other data where possible so that the error can be reproduced.

-

Documentation

- -

If you would like to extend the documentation of this library, or translate the documentation into other languages, please also open an issue, and I’ll contact you for further discussions.

- -
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.js b/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.js deleted file mode 100755 index 1984416..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.js +++ /dev/null @@ -1,74 +0,0 @@ -// Jazzy - https://github.com/realm/jazzy -// Copyright Realm Inc. -// SPDX-License-Identifier: MIT - -window.jazzy = {'docset': false} -if (typeof window.dash != 'undefined') { - document.documentElement.className += ' dash' - window.jazzy.docset = true -} -if (navigator.userAgent.match(/xcode/i)) { - document.documentElement.className += ' xcode' - window.jazzy.docset = true -} - -function toggleItem($link, $content) { - var animationDuration = 300; - $link.toggleClass('token-open'); - $content.slideToggle(animationDuration); -} - -function itemLinkToContent($link) { - return $link.parent().parent().next(); -} - -// On doc load + hash-change, open any targetted item -function openCurrentItemIfClosed() { - if (window.jazzy.docset) { - return; - } - var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); - $content = itemLinkToContent($link); - if ($content.is(':hidden')) { - toggleItem($link, $content); - } -} - -$(openCurrentItemIfClosed); -$(window).on('hashchange', openCurrentItemIfClosed); - -// On item link ('token') click, toggle its discussion -$('.token').on('click', function(event) { - if (window.jazzy.docset) { - return; - } - var $link = $(this); - toggleItem($link, itemLinkToContent($link)); - - // Keeps the document from jumping to the hash. - var href = $link.attr('href'); - if (history.pushState) { - history.pushState({}, '', href); - } else { - location.hash = href; - } - event.preventDefault(); -}); - -// Clicks on links to the current, closed, item need to open the item -$("a:not('.token')").on('click', function() { - if (location == this.href) { - openCurrentItemIfClosed(); - } -}); - -// KaTeX rendering -if ("katex" in window) { - $($('.math').each( (_, element) => { - katex.render(element.textContent, element, { - displayMode: $(element).hasClass('m-block'), - throwOnError: false, - trust: true - }); - })) -} diff --git a/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.search.js b/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.search.js deleted file mode 100644 index 359cdbb..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.search.js +++ /dev/null @@ -1,74 +0,0 @@ -// Jazzy - https://github.com/realm/jazzy -// Copyright Realm Inc. -// SPDX-License-Identifier: MIT - -$(function(){ - var $typeahead = $('[data-typeahead]'); - var $form = $typeahead.parents('form'); - var searchURL = $form.attr('action'); - - function displayTemplate(result) { - return result.name; - } - - function suggestionTemplate(result) { - var t = '
'; - t += '' + result.name + ''; - if (result.parent_name) { - t += '' + result.parent_name + ''; - } - t += '
'; - return t; - } - - $typeahead.one('focus', function() { - $form.addClass('loading'); - - $.getJSON(searchURL).then(function(searchData) { - const searchIndex = lunr(function() { - this.ref('url'); - this.field('name'); - this.field('abstract'); - for (const [url, doc] of Object.entries(searchData)) { - this.add({url: url, name: doc.name, abstract: doc.abstract}); - } - }); - - $typeahead.typeahead( - { - highlight: true, - minLength: 3, - autoselect: true - }, - { - limit: 10, - display: displayTemplate, - templates: { suggestion: suggestionTemplate }, - source: function(query, sync) { - const lcSearch = query.toLowerCase(); - const results = searchIndex.query(function(q) { - q.term(lcSearch, { boost: 100 }); - q.term(lcSearch, { - boost: 10, - wildcard: lunr.Query.wildcard.TRAILING - }); - }).map(function(result) { - var doc = searchData[result.ref]; - doc.url = result.ref; - return doc; - }); - sync(results); - } - } - ); - $form.removeClass('loading'); - $typeahead.trigger('focus'); - }); - }); - - var baseURL = searchURL.slice(0, -"search.json".length); - - $typeahead.on('typeahead:select', function(e, result) { - window.location = baseURL + result.url; - }); -}); diff --git a/docs/docsets/.docset/Contents/Resources/Documents/js/jquery.min.js b/docs/docsets/.docset/Contents/Resources/Documents/js/jquery.min.js deleted file mode 100644 index 7f37b5d..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/js/jquery.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 00){var c=e.utils.clone(r)||{};c.position=[a,l],c.index=s.length,s.push(new e.Token(i.slice(a,o),c))}a=o+1}}return s},e.tokenizer.separator=/[\s\-]+/,e.Pipeline=function(){this._stack=[]},e.Pipeline.registeredFunctions=Object.create(null),e.Pipeline.registerFunction=function(t,r){r in this.registeredFunctions&&e.utils.warn("Overwriting existing registered function: "+r),t.label=r,e.Pipeline.registeredFunctions[t.label]=t},e.Pipeline.warnIfFunctionNotRegistered=function(t){var r=t.label&&t.label in this.registeredFunctions;r||e.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",t)},e.Pipeline.load=function(t){var r=new e.Pipeline;return t.forEach(function(t){var i=e.Pipeline.registeredFunctions[t];if(!i)throw new Error("Cannot load unregistered function: "+t);r.add(i)}),r},e.Pipeline.prototype.add=function(){var t=Array.prototype.slice.call(arguments);t.forEach(function(t){e.Pipeline.warnIfFunctionNotRegistered(t),this._stack.push(t)},this)},e.Pipeline.prototype.after=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,r)},e.Pipeline.prototype.before=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");this._stack.splice(i,0,r)},e.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);t!=-1&&this._stack.splice(t,1)},e.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r1&&(se&&(r=n),s!=e);)i=r-t,n=t+Math.floor(i/2),s=this.elements[2*n];return s==e?2*n:s>e?2*n:sa?l+=2:o==a&&(t+=r[u+1]*i[l+1],u+=2,l+=2);return t},e.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},e.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t0){var o,a=s.str.charAt(0);a in s.node.edges?o=s.node.edges[a]:(o=new e.TokenSet,s.node.edges[a]=o),1==s.str.length&&(o["final"]=!0),n.push({node:o,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(0!=s.editsRemaining){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new e.TokenSet;s.node.edges["*"]=u}if(0==s.str.length&&(u["final"]=!0),n.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&n.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),1==s.str.length&&(s.node["final"]=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new e.TokenSet;s.node.edges["*"]=l}1==s.str.length&&(l["final"]=!0),n.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var c,h=s.str.charAt(0),d=s.str.charAt(1);d in s.node.edges?c=s.node.edges[d]:(c=new e.TokenSet,s.node.edges[d]=c),1==s.str.length&&(c["final"]=!0),n.push({node:c,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return i},e.TokenSet.fromString=function(t){for(var r=new e.TokenSet,i=r,n=0,s=t.length;n=e;t--){var r=this.uncheckedNodes[t],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r["char"]]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}},e.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},e.Index.prototype.search=function(t){return this.query(function(r){var i=new e.QueryParser(t,r);i.parse()})},e.Index.prototype.query=function(t){for(var r=new e.Query(this.fields),i=Object.create(null),n=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},e.Builder.prototype.k1=function(e){this._k1=e},e.Builder.prototype.add=function(t,r){var i=t[this._ref],n=Object.keys(this._fields);this._documents[i]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return e.QueryLexer.EOS;var t=this.str.charAt(this.pos);return this.pos+=1,t},e.QueryLexer.prototype.width=function(){return this.pos-this.start},e.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},e.QueryLexer.prototype.backup=function(){this.pos-=1},e.QueryLexer.prototype.acceptDigitRun=function(){var t,r;do t=this.next(),r=t.charCodeAt(0);while(r>47&&r<58);t!=e.QueryLexer.EOS&&this.backup()},e.QueryLexer.prototype.more=function(){return this.pos1&&(t.backup(),t.emit(e.QueryLexer.TERM)),t.ignore(),t.more())return e.QueryLexer.lexText},e.QueryLexer.lexEditDistance=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.EDIT_DISTANCE),e.QueryLexer.lexText},e.QueryLexer.lexBoost=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.BOOST),e.QueryLexer.lexText},e.QueryLexer.lexEOS=function(t){t.width()>0&&t.emit(e.QueryLexer.TERM)},e.QueryLexer.termSeparator=e.tokenizer.separator,e.QueryLexer.lexText=function(t){for(;;){var r=t.next();if(r==e.QueryLexer.EOS)return e.QueryLexer.lexEOS;if(92!=r.charCodeAt(0)){if(":"==r)return e.QueryLexer.lexField;if("~"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexEditDistance;if("^"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexBoost;if("+"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if("-"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if(r.match(e.QueryLexer.termSeparator))return e.QueryLexer.lexTerm}else t.escapeCharacter()}},e.QueryParser=function(t,r){this.lexer=new e.QueryLexer(t),this.query=r,this.currentClause={},this.lexemeIdx=0},e.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var t=e.QueryParser.parseClause;t;)t=t(this);return this.query},e.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},e.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},e.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},e.QueryParser.parseClause=function(t){var r=t.peekLexeme();if(void 0!=r)switch(r.type){case e.QueryLexer.PRESENCE:return e.QueryParser.parsePresence;case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(i+=" with value '"+r.str+"'"),new e.QueryParseError(i,r.start,r.end)}},e.QueryParser.parsePresence=function(t){var r=t.consumeLexeme();if(void 0!=r){switch(r.str){case"-":t.currentClause.presence=e.Query.presence.PROHIBITED;break;case"+":t.currentClause.presence=e.Query.presence.REQUIRED;break;default:var i="unrecognised presence operator'"+r.str+"'";throw new e.QueryParseError(i,r.start,r.end)}var n=t.peekLexeme();if(void 0==n){var i="expecting term or field, found nothing";throw new e.QueryParseError(i,r.start,r.end)}switch(n.type){case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expecting term or field, found '"+n.type+"'";throw new e.QueryParseError(i,n.start,n.end)}}},e.QueryParser.parseField=function(t){var r=t.consumeLexeme();if(void 0!=r){if(t.query.allFields.indexOf(r.str)==-1){var i=t.query.allFields.map(function(e){return"'"+e+"'"}).join(", "),n="unrecognised field '"+r.str+"', possible fields: "+i;throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.fields=[r.str];var s=t.peekLexeme();if(void 0==s){var n="expecting term, found nothing";throw new e.QueryParseError(n,r.start,r.end)}switch(s.type){case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var n="expecting term, found '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseTerm=function(t){var r=t.consumeLexeme();if(void 0!=r){t.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(t.currentClause.usePipeline=!1);var i=t.peekLexeme();if(void 0==i)return void t.nextClause();switch(i.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+i.type+"'";throw new e.QueryParseError(n,i.start,i.end)}}},e.QueryParser.parseEditDistance=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="edit distance must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.editDistance=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseBoost=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="boost must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.boost=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.lunr=t()}(this,function(){return e})}(); diff --git a/docs/docsets/.docset/Contents/Resources/Documents/js/typeahead.jquery.js b/docs/docsets/.docset/Contents/Resources/Documents/js/typeahead.jquery.js deleted file mode 100644 index 3a2d2ab..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/js/typeahead.jquery.js +++ /dev/null @@ -1,1694 +0,0 @@ -/*! - * typeahead.js 1.3.1 - * https://github.com/corejavascript/typeahead.js - * Copyright 2013-2020 Twitter, Inc. and other contributors; Licensed MIT - */ - - -(function(root, factory) { - if (typeof define === "function" && define.amd) { - define([ "jquery" ], function(a0) { - return factory(a0); - }); - } else if (typeof module === "object" && module.exports) { - module.exports = factory(require("jquery")); - } else { - factory(root["jQuery"]); - } -})(this, function($) { - var _ = function() { - "use strict"; - return { - isMsie: function() { - return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; - }, - isBlankString: function(str) { - return !str || /^\s*$/.test(str); - }, - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - isString: function(obj) { - return typeof obj === "string"; - }, - isNumber: function(obj) { - return typeof obj === "number"; - }, - isArray: $.isArray, - isFunction: $.isFunction, - isObject: $.isPlainObject, - isUndefined: function(obj) { - return typeof obj === "undefined"; - }, - isElement: function(obj) { - return !!(obj && obj.nodeType === 1); - }, - isJQuery: function(obj) { - return obj instanceof $; - }, - toStr: function toStr(s) { - return _.isUndefined(s) || s === null ? "" : s + ""; - }, - bind: $.proxy, - each: function(collection, cb) { - $.each(collection, reverseArgs); - function reverseArgs(index, value) { - return cb(value, index); - } - }, - map: $.map, - filter: $.grep, - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (!(result = test.call(null, val, key, obj))) { - return false; - } - }); - return !!result; - }, - some: function(obj, test) { - var result = false; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (result = test.call(null, val, key, obj)) { - return false; - } - }); - return !!result; - }, - mixin: $.extend, - identity: function(x) { - return x; - }, - clone: function(obj) { - return $.extend(true, {}, obj); - }, - getIdGenerator: function() { - var counter = 0; - return function() { - return counter++; - }; - }, - templatify: function templatify(obj) { - return $.isFunction(obj) ? obj : template; - function template() { - return String(obj); - } - }, - defer: function(fn) { - setTimeout(fn, 0); - }, - debounce: function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments, later, callNow; - later = function() { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - } - }; - callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - } - return result; - }; - }, - throttle: function(func, wait) { - var context, args, timeout, result, previous, later; - previous = 0; - later = function() { - previous = new Date(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date(), remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - stringify: function(val) { - return _.isString(val) ? val : JSON.stringify(val); - }, - guid: function() { - function _p8(s) { - var p = (Math.random().toString(16) + "000000000").substr(2, 8); - return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p; - } - return "tt-" + _p8() + _p8(true) + _p8(true) + _p8(); - }, - noop: function() {} - }; - }(); - var WWW = function() { - "use strict"; - var defaultClassNames = { - wrapper: "twitter-typeahead", - input: "tt-input", - hint: "tt-hint", - menu: "tt-menu", - dataset: "tt-dataset", - suggestion: "tt-suggestion", - selectable: "tt-selectable", - empty: "tt-empty", - open: "tt-open", - cursor: "tt-cursor", - highlight: "tt-highlight" - }; - return build; - function build(o) { - var www, classes; - classes = _.mixin({}, defaultClassNames, o); - www = { - css: buildCss(), - classes: classes, - html: buildHtml(classes), - selectors: buildSelectors(classes) - }; - return { - css: www.css, - html: www.html, - classes: www.classes, - selectors: www.selectors, - mixin: function(o) { - _.mixin(o, www); - } - }; - } - function buildHtml(c) { - return { - wrapper: '', - menu: '
' - }; - } - function buildSelectors(classes) { - var selectors = {}; - _.each(classes, function(v, k) { - selectors[k] = "." + v; - }); - return selectors; - } - function buildCss() { - var css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none", - opacity: "1" - }, - input: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - inputWithNoHint: { - position: "relative", - verticalAlign: "top" - }, - menu: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" - }, - ltr: { - left: "0", - right: "auto" - }, - rtl: { - left: "auto", - right: " 0" - } - }; - if (_.isMsie()) { - _.mixin(css.input, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); - } - return css; - } - }(); - var EventBus = function() { - "use strict"; - var namespace, deprecationMap; - namespace = "typeahead:"; - deprecationMap = { - render: "rendered", - cursorchange: "cursorchanged", - select: "selected", - autocomplete: "autocompleted" - }; - function EventBus(o) { - if (!o || !o.el) { - $.error("EventBus initialized without el"); - } - this.$el = $(o.el); - } - _.mixin(EventBus.prototype, { - _trigger: function(type, args) { - var $e = $.Event(namespace + type); - this.$el.trigger.call(this.$el, $e, args || []); - return $e; - }, - before: function(type) { - var args, $e; - args = [].slice.call(arguments, 1); - $e = this._trigger("before" + type, args); - return $e.isDefaultPrevented(); - }, - trigger: function(type) { - var deprecatedType; - this._trigger(type, [].slice.call(arguments, 1)); - if (deprecatedType = deprecationMap[type]) { - this._trigger(deprecatedType, [].slice.call(arguments, 1)); - } - } - }); - return EventBus; - }(); - var EventEmitter = function() { - "use strict"; - var splitter = /\s+/, nextTick = getNextTick(); - return { - onSync: onSync, - onAsync: onAsync, - off: off, - trigger: trigger - }; - function on(method, types, cb, context) { - var type; - if (!cb) { - return this; - } - types = types.split(splitter); - cb = context ? bindContext(cb, context) : cb; - this._callbacks = this._callbacks || {}; - while (type = types.shift()) { - this._callbacks[type] = this._callbacks[type] || { - sync: [], - async: [] - }; - this._callbacks[type][method].push(cb); - } - return this; - } - function onAsync(types, cb, context) { - return on.call(this, "async", types, cb, context); - } - function onSync(types, cb, context) { - return on.call(this, "sync", types, cb, context); - } - function off(types) { - var type; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - while (type = types.shift()) { - delete this._callbacks[type]; - } - return this; - } - function trigger(types) { - var type, callbacks, args, syncFlush, asyncFlush; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - args = [].slice.call(arguments, 1); - while ((type = types.shift()) && (callbacks = this._callbacks[type])) { - syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); - asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); - syncFlush() && nextTick(asyncFlush); - } - return this; - } - function getFlush(callbacks, context, args) { - return flush; - function flush() { - var cancelled; - for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { - cancelled = callbacks[i].apply(context, args) === false; - } - return !cancelled; - } - } - function getNextTick() { - var nextTickFn; - if (window.setImmediate) { - nextTickFn = function nextTickSetImmediate(fn) { - setImmediate(function() { - fn(); - }); - }; - } else { - nextTickFn = function nextTickSetTimeout(fn) { - setTimeout(function() { - fn(); - }, 0); - }; - } - return nextTickFn; - } - function bindContext(fn, context) { - return fn.bind ? fn.bind(context) : function() { - fn.apply(context, [].slice.call(arguments, 0)); - }; - } - }(); - var highlight = function(doc) { - "use strict"; - var defaults = { - node: null, - pattern: null, - tagName: "strong", - className: null, - wordsOnly: false, - caseSensitive: false, - diacriticInsensitive: false - }; - var accented = { - A: "[AaªÀ-Åà-åĀ-ąǍǎȀ-ȃȦȧᴬᵃḀḁẚẠ-ảₐ℀℁℻⒜Ⓐⓐ㍱-㍴㎀-㎄㎈㎉㎩-㎯㏂㏊㏟㏿Aa]", - B: "[BbᴮᵇḂ-ḇℬ⒝Ⓑⓑ㍴㎅-㎇㏃㏈㏔㏝Bb]", - C: "[CcÇçĆ-čᶜ℀ℂ℃℅℆ℭⅭⅽ⒞Ⓒⓒ㍶㎈㎉㎝㎠㎤㏄-㏇Cc]", - D: "[DdĎďDŽ-džDZ-dzᴰᵈḊ-ḓⅅⅆⅮⅾ⒟Ⓓⓓ㋏㍲㍷-㍹㎗㎭-㎯㏅㏈Dd]", - E: "[EeÈ-Ëè-ëĒ-ěȄ-ȇȨȩᴱᵉḘ-ḛẸ-ẽₑ℡ℯℰⅇ⒠Ⓔⓔ㉐㋍㋎Ee]", - F: "[FfᶠḞḟ℉ℱ℻⒡Ⓕⓕ㎊-㎌㎙ff-fflFf]", - G: "[GgĜ-ģǦǧǴǵᴳᵍḠḡℊ⒢Ⓖⓖ㋌㋍㎇㎍-㎏㎓㎬㏆㏉㏒㏿Gg]", - H: "[HhĤĥȞȟʰᴴḢ-ḫẖℋ-ℎ⒣Ⓗⓗ㋌㍱㎐-㎔㏊㏋㏗Hh]", - I: "[IiÌ-Ïì-ïĨ-İIJijǏǐȈ-ȋᴵᵢḬḭỈ-ịⁱℐℑℹⅈⅠ-ⅣⅥ-ⅨⅪⅫⅰ-ⅳⅵ-ⅸⅺⅻ⒤Ⓘⓘ㍺㏌㏕fiffiIi]", - J: "[JjIJ-ĵLJ-njǰʲᴶⅉ⒥ⒿⓙⱼJj]", - K: "[KkĶķǨǩᴷᵏḰ-ḵK⒦Ⓚⓚ㎄㎅㎉㎏㎑㎘㎞㎢㎦㎪㎸㎾㏀㏆㏍-㏏Kk]", - L: "[LlĹ-ŀLJ-ljˡᴸḶḷḺ-ḽℒℓ℡Ⅼⅼ⒧Ⓛⓛ㋏㎈㎉㏐-㏓㏕㏖㏿flfflLl]", - M: "[MmᴹᵐḾ-ṃ℠™ℳⅯⅿ⒨Ⓜⓜ㍷-㍹㎃㎆㎎㎒㎖㎙-㎨㎫㎳㎷㎹㎽㎿㏁㏂㏎㏐㏔-㏖㏘㏙㏞㏟Mm]", - N: "[NnÑñŃ-ʼnNJ-njǸǹᴺṄ-ṋⁿℕ№⒩Ⓝⓝ㎁㎋㎚㎱㎵㎻㏌㏑Nn]", - O: "[OoºÒ-Öò-öŌ-őƠơǑǒǪǫȌ-ȏȮȯᴼᵒỌ-ỏₒ℅№ℴ⒪Ⓞⓞ㍵㏇㏒㏖Oo]", - P: "[PpᴾᵖṔ-ṗℙ⒫Ⓟⓟ㉐㍱㍶㎀㎊㎩-㎬㎰㎴㎺㏋㏗-㏚Pp]", - Q: "[Qqℚ⒬Ⓠⓠ㏃Qq]", - R: "[RrŔ-řȐ-ȓʳᴿᵣṘ-ṛṞṟ₨ℛ-ℝ⒭Ⓡⓡ㋍㍴㎭-㎯㏚㏛Rr]", - S: "[SsŚ-šſȘșˢṠ-ṣ₨℁℠⒮Ⓢⓢ㎧㎨㎮-㎳㏛㏜stSs]", - T: "[TtŢ-ťȚțᵀᵗṪ-ṱẗ℡™⒯Ⓣⓣ㉐㋏㎔㏏ſtstTt]", - U: "[UuÙ-Üù-üŨ-ųƯưǓǔȔ-ȗᵁᵘᵤṲ-ṷỤ-ủ℆⒰Ⓤⓤ㍳㍺Uu]", - V: "[VvᵛᵥṼ-ṿⅣ-Ⅷⅳ-ⅷ⒱Ⓥⓥⱽ㋎㍵㎴-㎹㏜㏞Vv]", - W: "[WwŴŵʷᵂẀ-ẉẘ⒲Ⓦⓦ㎺-㎿㏝Ww]", - X: "[XxˣẊ-ẍₓ℻Ⅸ-Ⅻⅸ-ⅻ⒳Ⓧⓧ㏓Xx]", - Y: "[YyÝýÿŶ-ŸȲȳʸẎẏẙỲ-ỹ⒴Ⓨⓨ㏉Yy]", - Z: "[ZzŹ-žDZ-dzᶻẐ-ẕℤℨ⒵Ⓩⓩ㎐-㎔Zz]" - }; - return function hightlight(o) { - var regex; - o = _.mixin({}, defaults, o); - if (!o.node || !o.pattern) { - return; - } - o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; - regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly, o.diacriticInsensitive); - traverse(o.node, hightlightTextNode); - function hightlightTextNode(textNode) { - var match, patternNode, wrapperNode; - if (match = regex.exec(textNode.data)) { - wrapperNode = doc.createElement(o.tagName); - o.className && (wrapperNode.className = o.className); - patternNode = textNode.splitText(match.index); - patternNode.splitText(match[0].length); - wrapperNode.appendChild(patternNode.cloneNode(true)); - textNode.parentNode.replaceChild(wrapperNode, patternNode); - } - return !!match; - } - function traverse(el, hightlightTextNode) { - var childNode, TEXT_NODE_TYPE = 3; - for (var i = 0; i < el.childNodes.length; i++) { - childNode = el.childNodes[i]; - if (childNode.nodeType === TEXT_NODE_TYPE) { - i += hightlightTextNode(childNode) ? 1 : 0; - } else { - traverse(childNode, hightlightTextNode); - } - } - } - }; - function accent_replacer(chr) { - return accented[chr.toUpperCase()] || chr; - } - function getRegex(patterns, caseSensitive, wordsOnly, diacriticInsensitive) { - var escapedPatterns = [], regexStr; - for (var i = 0, len = patterns.length; i < len; i++) { - var escapedWord = _.escapeRegExChars(patterns[i]); - if (diacriticInsensitive) { - escapedWord = escapedWord.replace(/\S/g, accent_replacer); - } - escapedPatterns.push(escapedWord); - } - regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; - return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); - } - }(window.document); - var Input = function() { - "use strict"; - var specialKeyCodeMap; - specialKeyCodeMap = { - 9: "tab", - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - function Input(o, www) { - var id; - o = o || {}; - if (!o.input) { - $.error("input is missing"); - } - www.mixin(this); - this.$hint = $(o.hint); - this.$input = $(o.input); - this.$menu = $(o.menu); - id = this.$input.attr("id") || _.guid(); - this.$menu.attr("id", id + "_listbox"); - this.$hint.attr({ - "aria-hidden": true - }); - this.$input.attr({ - "aria-owns": id + "_listbox", - role: "combobox", - "aria-autocomplete": "list", - "aria-expanded": false - }); - this.query = this.$input.val(); - this.queryWhenFocused = this.hasFocus() ? this.query : null; - this.$overflowHelper = buildOverflowHelper(this.$input); - this._checkLanguageDirection(); - if (this.$hint.length === 0) { - this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; - } - this.onSync("cursorchange", this._updateDescendent); - } - Input.normalizeQuery = function(str) { - return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - }; - _.mixin(Input.prototype, EventEmitter, { - _onBlur: function onBlur() { - this.resetInputValue(); - this.trigger("blurred"); - }, - _onFocus: function onFocus() { - this.queryWhenFocused = this.query; - this.trigger("focused"); - }, - _onKeydown: function onKeydown($e) { - var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; - this._managePreventDefault(keyName, $e); - if (keyName && this._shouldTrigger(keyName, $e)) { - this.trigger(keyName + "Keyed", $e); - } - }, - _onInput: function onInput() { - this._setQuery(this.getInputValue()); - this.clearHintIfInvalid(); - this._checkLanguageDirection(); - }, - _managePreventDefault: function managePreventDefault(keyName, $e) { - var preventDefault; - switch (keyName) { - case "up": - case "down": - preventDefault = !withModifier($e); - break; - - default: - preventDefault = false; - } - preventDefault && $e.preventDefault(); - }, - _shouldTrigger: function shouldTrigger(keyName, $e) { - var trigger; - switch (keyName) { - case "tab": - trigger = !withModifier($e); - break; - - default: - trigger = true; - } - return trigger; - }, - _checkLanguageDirection: function checkLanguageDirection() { - var dir = (this.$input.css("direction") || "ltr").toLowerCase(); - if (this.dir !== dir) { - this.dir = dir; - this.$hint.attr("dir", dir); - this.trigger("langDirChanged", dir); - } - }, - _setQuery: function setQuery(val, silent) { - var areEquivalent, hasDifferentWhitespace; - areEquivalent = areQueriesEquivalent(val, this.query); - hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; - this.query = val; - if (!silent && !areEquivalent) { - this.trigger("queryChanged", this.query); - } else if (!silent && hasDifferentWhitespace) { - this.trigger("whitespaceChanged", this.query); - } - }, - _updateDescendent: function updateDescendent(event, id) { - this.$input.attr("aria-activedescendant", id); - }, - bind: function() { - var that = this, onBlur, onFocus, onKeydown, onInput; - onBlur = _.bind(this._onBlur, this); - onFocus = _.bind(this._onFocus, this); - onKeydown = _.bind(this._onKeydown, this); - onInput = _.bind(this._onInput, this); - this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); - if (!_.isMsie() || _.isMsie() > 9) { - this.$input.on("input.tt", onInput); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - _.defer(_.bind(that._onInput, that, $e)); - }); - } - return this; - }, - focus: function focus() { - this.$input.focus(); - }, - blur: function blur() { - this.$input.blur(); - }, - getLangDir: function getLangDir() { - return this.dir; - }, - getQuery: function getQuery() { - return this.query || ""; - }, - setQuery: function setQuery(val, silent) { - this.setInputValue(val); - this._setQuery(val, silent); - }, - hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { - return this.query !== this.queryWhenFocused; - }, - getInputValue: function getInputValue() { - return this.$input.val(); - }, - setInputValue: function setInputValue(value) { - this.$input.val(value); - this.clearHintIfInvalid(); - this._checkLanguageDirection(); - }, - resetInputValue: function resetInputValue() { - this.setInputValue(this.query); - }, - getHint: function getHint() { - return this.$hint.val(); - }, - setHint: function setHint(value) { - this.$hint.val(value); - }, - clearHint: function clearHint() { - this.setHint(""); - }, - clearHintIfInvalid: function clearHintIfInvalid() { - var val, hint, valIsPrefixOfHint, isValid; - val = this.getInputValue(); - hint = this.getHint(); - valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; - isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); - !isValid && this.clearHint(); - }, - hasFocus: function hasFocus() { - return this.$input.is(":focus"); - }, - hasOverflow: function hasOverflow() { - var constraint = this.$input.width() - 2; - this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() >= constraint; - }, - isCursorAtEnd: function() { - var valueLength, selectionStart, range; - valueLength = this.$input.val().length; - selectionStart = this.$input[0].selectionStart; - if (_.isNumber(selectionStart)) { - return selectionStart === valueLength; - } else if (document.selection) { - range = document.selection.createRange(); - range.moveStart("character", -valueLength); - return valueLength === range.text.length; - } - return true; - }, - destroy: function destroy() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$overflowHelper.remove(); - this.$hint = this.$input = this.$overflowHelper = $("
"); - }, - setAriaExpanded: function setAriaExpanded(value) { - this.$input.attr("aria-expanded", value); - } - }); - return Input; - function buildOverflowHelper($input) { - return $('').css({ - position: "absolute", - visibility: "hidden", - whiteSpace: "pre", - fontFamily: $input.css("font-family"), - fontSize: $input.css("font-size"), - fontStyle: $input.css("font-style"), - fontVariant: $input.css("font-variant"), - fontWeight: $input.css("font-weight"), - wordSpacing: $input.css("word-spacing"), - letterSpacing: $input.css("letter-spacing"), - textIndent: $input.css("text-indent"), - textRendering: $input.css("text-rendering"), - textTransform: $input.css("text-transform") - }).insertAfter($input); - } - function areQueriesEquivalent(a, b) { - return Input.normalizeQuery(a) === Input.normalizeQuery(b); - } - function withModifier($e) { - return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; - } - }(); - var Dataset = function() { - "use strict"; - var keys, nameGenerator; - keys = { - dataset: "tt-selectable-dataset", - val: "tt-selectable-display", - obj: "tt-selectable-object" - }; - nameGenerator = _.getIdGenerator(); - function Dataset(o, www) { - o = o || {}; - o.templates = o.templates || {}; - o.templates.notFound = o.templates.notFound || o.templates.empty; - if (!o.source) { - $.error("missing source"); - } - if (!o.node) { - $.error("missing node"); - } - if (o.name && !isValidName(o.name)) { - $.error("invalid dataset name: " + o.name); - } - www.mixin(this); - this.highlight = !!o.highlight; - this.name = _.toStr(o.name || nameGenerator()); - this.limit = o.limit || 5; - this.displayFn = getDisplayFn(o.display || o.displayKey); - this.templates = getTemplates(o.templates, this.displayFn); - this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; - this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; - this._resetLastSuggestion(); - this.$el = $(o.node).attr("role", "presentation").addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); - } - Dataset.extractData = function extractData(el) { - var $el = $(el); - if ($el.data(keys.obj)) { - return { - dataset: $el.data(keys.dataset) || "", - val: $el.data(keys.val) || "", - obj: $el.data(keys.obj) || null - }; - } - return null; - }; - _.mixin(Dataset.prototype, EventEmitter, { - _overwrite: function overwrite(query, suggestions) { - suggestions = suggestions || []; - if (suggestions.length) { - this._renderSuggestions(query, suggestions); - } else if (this.async && this.templates.pending) { - this._renderPending(query); - } else if (!this.async && this.templates.notFound) { - this._renderNotFound(query); - } else { - this._empty(); - } - this.trigger("rendered", suggestions, false, this.name); - }, - _append: function append(query, suggestions) { - suggestions = suggestions || []; - if (suggestions.length && this.$lastSuggestion.length) { - this._appendSuggestions(query, suggestions); - } else if (suggestions.length) { - this._renderSuggestions(query, suggestions); - } else if (!this.$lastSuggestion.length && this.templates.notFound) { - this._renderNotFound(query); - } - this.trigger("rendered", suggestions, true, this.name); - }, - _renderSuggestions: function renderSuggestions(query, suggestions) { - var $fragment; - $fragment = this._getSuggestionsFragment(query, suggestions); - this.$lastSuggestion = $fragment.children().last(); - this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); - }, - _appendSuggestions: function appendSuggestions(query, suggestions) { - var $fragment, $lastSuggestion; - $fragment = this._getSuggestionsFragment(query, suggestions); - $lastSuggestion = $fragment.children().last(); - this.$lastSuggestion.after($fragment); - this.$lastSuggestion = $lastSuggestion; - }, - _renderPending: function renderPending(query) { - var template = this.templates.pending; - this._resetLastSuggestion(); - template && this.$el.html(template({ - query: query, - dataset: this.name - })); - }, - _renderNotFound: function renderNotFound(query) { - var template = this.templates.notFound; - this._resetLastSuggestion(); - template && this.$el.html(template({ - query: query, - dataset: this.name - })); - }, - _empty: function empty() { - this.$el.empty(); - this._resetLastSuggestion(); - }, - _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { - var that = this, fragment; - fragment = document.createDocumentFragment(); - _.each(suggestions, function getSuggestionNode(suggestion) { - var $el, context; - context = that._injectQuery(query, suggestion); - $el = $(that.templates.suggestion(context)).data(keys.dataset, that.name).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); - fragment.appendChild($el[0]); - }); - this.highlight && highlight({ - className: this.classes.highlight, - node: fragment, - pattern: query - }); - return $(fragment); - }, - _getFooter: function getFooter(query, suggestions) { - return this.templates.footer ? this.templates.footer({ - query: query, - suggestions: suggestions, - dataset: this.name - }) : null; - }, - _getHeader: function getHeader(query, suggestions) { - return this.templates.header ? this.templates.header({ - query: query, - suggestions: suggestions, - dataset: this.name - }) : null; - }, - _resetLastSuggestion: function resetLastSuggestion() { - this.$lastSuggestion = $(); - }, - _injectQuery: function injectQuery(query, obj) { - return _.isObject(obj) ? _.mixin({ - _query: query - }, obj) : obj; - }, - update: function update(query) { - var that = this, canceled = false, syncCalled = false, rendered = 0; - this.cancel(); - this.cancel = function cancel() { - canceled = true; - that.cancel = $.noop; - that.async && that.trigger("asyncCanceled", query, that.name); - }; - this.source(query, sync, async); - !syncCalled && sync([]); - function sync(suggestions) { - if (syncCalled) { - return; - } - syncCalled = true; - suggestions = (suggestions || []).slice(0, that.limit); - rendered = suggestions.length; - that._overwrite(query, suggestions); - if (rendered < that.limit && that.async) { - that.trigger("asyncRequested", query, that.name); - } - } - function async(suggestions) { - suggestions = suggestions || []; - if (!canceled && rendered < that.limit) { - that.cancel = $.noop; - var idx = Math.abs(rendered - that.limit); - rendered += idx; - that._append(query, suggestions.slice(0, idx)); - that.async && that.trigger("asyncReceived", query, that.name); - } - } - }, - cancel: $.noop, - clear: function clear() { - this._empty(); - this.cancel(); - this.trigger("cleared"); - }, - isEmpty: function isEmpty() { - return this.$el.is(":empty"); - }, - destroy: function destroy() { - this.$el = $("
"); - } - }); - return Dataset; - function getDisplayFn(display) { - display = display || _.stringify; - return _.isFunction(display) ? display : displayFn; - function displayFn(obj) { - return obj[display]; - } - } - function getTemplates(templates, displayFn) { - return { - notFound: templates.notFound && _.templatify(templates.notFound), - pending: templates.pending && _.templatify(templates.pending), - header: templates.header && _.templatify(templates.header), - footer: templates.footer && _.templatify(templates.footer), - suggestion: templates.suggestion ? userSuggestionTemplate : suggestionTemplate - }; - function userSuggestionTemplate(context) { - var template = templates.suggestion; - return $(template(context)).attr("id", _.guid()); - } - function suggestionTemplate(context) { - return $('
').attr("id", _.guid()).text(displayFn(context)); - } - } - function isValidName(str) { - return /^[_a-zA-Z0-9-]+$/.test(str); - } - }(); - var Menu = function() { - "use strict"; - function Menu(o, www) { - var that = this; - o = o || {}; - if (!o.node) { - $.error("node is required"); - } - www.mixin(this); - this.$node = $(o.node); - this.query = null; - this.datasets = _.map(o.datasets, initializeDataset); - function initializeDataset(oDataset) { - var node = that.$node.find(oDataset.node).first(); - oDataset.node = node.length ? node : $("
").appendTo(that.$node); - return new Dataset(oDataset, www); - } - } - _.mixin(Menu.prototype, EventEmitter, { - _onSelectableClick: function onSelectableClick($e) { - this.trigger("selectableClicked", $($e.currentTarget)); - }, - _onRendered: function onRendered(type, dataset, suggestions, async) { - this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); - this.trigger("datasetRendered", dataset, suggestions, async); - }, - _onCleared: function onCleared() { - this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); - this.trigger("datasetCleared"); - }, - _propagate: function propagate() { - this.trigger.apply(this, arguments); - }, - _allDatasetsEmpty: function allDatasetsEmpty() { - return _.every(this.datasets, _.bind(function isDatasetEmpty(dataset) { - var isEmpty = dataset.isEmpty(); - this.$node.attr("aria-expanded", !isEmpty); - return isEmpty; - }, this)); - }, - _getSelectables: function getSelectables() { - return this.$node.find(this.selectors.selectable); - }, - _removeCursor: function _removeCursor() { - var $selectable = this.getActiveSelectable(); - $selectable && $selectable.removeClass(this.classes.cursor); - }, - _ensureVisible: function ensureVisible($el) { - var elTop, elBottom, nodeScrollTop, nodeHeight; - elTop = $el.position().top; - elBottom = elTop + $el.outerHeight(true); - nodeScrollTop = this.$node.scrollTop(); - nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); - if (elTop < 0) { - this.$node.scrollTop(nodeScrollTop + elTop); - } else if (nodeHeight < elBottom) { - this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); - } - }, - bind: function() { - var that = this, onSelectableClick; - onSelectableClick = _.bind(this._onSelectableClick, this); - this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); - this.$node.on("mouseover", this.selectors.selectable, function() { - that.setCursor($(this)); - }); - this.$node.on("mouseleave", function() { - that._removeCursor(); - }); - _.each(this.datasets, function(dataset) { - dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); - }); - return this; - }, - isOpen: function isOpen() { - return this.$node.hasClass(this.classes.open); - }, - open: function open() { - this.$node.scrollTop(0); - this.$node.addClass(this.classes.open); - }, - close: function close() { - this.$node.attr("aria-expanded", false); - this.$node.removeClass(this.classes.open); - this._removeCursor(); - }, - setLanguageDirection: function setLanguageDirection(dir) { - this.$node.attr("dir", dir); - }, - selectableRelativeToCursor: function selectableRelativeToCursor(delta) { - var $selectables, $oldCursor, oldIndex, newIndex; - $oldCursor = this.getActiveSelectable(); - $selectables = this._getSelectables(); - oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; - newIndex = oldIndex + delta; - newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; - newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; - return newIndex === -1 ? null : $selectables.eq(newIndex); - }, - setCursor: function setCursor($selectable) { - this._removeCursor(); - if ($selectable = $selectable && $selectable.first()) { - $selectable.addClass(this.classes.cursor); - this._ensureVisible($selectable); - } - }, - getSelectableData: function getSelectableData($el) { - return $el && $el.length ? Dataset.extractData($el) : null; - }, - getActiveSelectable: function getActiveSelectable() { - var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); - return $selectable.length ? $selectable : null; - }, - getTopSelectable: function getTopSelectable() { - var $selectable = this._getSelectables().first(); - return $selectable.length ? $selectable : null; - }, - update: function update(query) { - var isValidUpdate = query !== this.query; - if (isValidUpdate) { - this.query = query; - _.each(this.datasets, updateDataset); - } - return isValidUpdate; - function updateDataset(dataset) { - dataset.update(query); - } - }, - empty: function empty() { - _.each(this.datasets, clearDataset); - this.query = null; - this.$node.addClass(this.classes.empty); - function clearDataset(dataset) { - dataset.clear(); - } - }, - destroy: function destroy() { - this.$node.off(".tt"); - this.$node = $("
"); - _.each(this.datasets, destroyDataset); - function destroyDataset(dataset) { - dataset.destroy(); - } - } - }); - return Menu; - }(); - var Status = function() { - "use strict"; - function Status(options) { - this.$el = $("", { - role: "status", - "aria-live": "polite" - }).css({ - position: "absolute", - padding: "0", - border: "0", - height: "1px", - width: "1px", - "margin-bottom": "-1px", - "margin-right": "-1px", - overflow: "hidden", - clip: "rect(0 0 0 0)", - "white-space": "nowrap" - }); - options.$input.after(this.$el); - _.each(options.menu.datasets, _.bind(function(dataset) { - if (dataset.onSync) { - dataset.onSync("rendered", _.bind(this.update, this)); - dataset.onSync("cleared", _.bind(this.cleared, this)); - } - }, this)); - } - _.mixin(Status.prototype, { - update: function update(event, suggestions) { - var length = suggestions.length; - var words; - if (length === 1) { - words = { - result: "result", - is: "is" - }; - } else { - words = { - result: "results", - is: "are" - }; - } - this.$el.text(length + " " + words.result + " " + words.is + " available, use up and down arrow keys to navigate."); - }, - cleared: function() { - this.$el.text(""); - } - }); - return Status; - }(); - var DefaultMenu = function() { - "use strict"; - var s = Menu.prototype; - function DefaultMenu() { - Menu.apply(this, [].slice.call(arguments, 0)); - } - _.mixin(DefaultMenu.prototype, Menu.prototype, { - open: function open() { - !this._allDatasetsEmpty() && this._show(); - return s.open.apply(this, [].slice.call(arguments, 0)); - }, - close: function close() { - this._hide(); - return s.close.apply(this, [].slice.call(arguments, 0)); - }, - _onRendered: function onRendered() { - if (this._allDatasetsEmpty()) { - this._hide(); - } else { - this.isOpen() && this._show(); - } - return s._onRendered.apply(this, [].slice.call(arguments, 0)); - }, - _onCleared: function onCleared() { - if (this._allDatasetsEmpty()) { - this._hide(); - } else { - this.isOpen() && this._show(); - } - return s._onCleared.apply(this, [].slice.call(arguments, 0)); - }, - setLanguageDirection: function setLanguageDirection(dir) { - this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); - return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); - }, - _hide: function hide() { - this.$node.hide(); - }, - _show: function show() { - this.$node.css("display", "block"); - } - }); - return DefaultMenu; - }(); - var Typeahead = function() { - "use strict"; - function Typeahead(o, www) { - var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; - o = o || {}; - if (!o.input) { - $.error("missing input"); - } - if (!o.menu) { - $.error("missing menu"); - } - if (!o.eventBus) { - $.error("missing event bus"); - } - www.mixin(this); - this.eventBus = o.eventBus; - this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; - this.input = o.input; - this.menu = o.menu; - this.enabled = true; - this.autoselect = !!o.autoselect; - this.active = false; - this.input.hasFocus() && this.activate(); - this.dir = this.input.getLangDir(); - this._hacks(); - this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); - onFocused = c(this, "activate", "open", "_onFocused"); - onBlurred = c(this, "deactivate", "_onBlurred"); - onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); - onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); - onEscKeyed = c(this, "isActive", "_onEscKeyed"); - onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); - onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); - onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); - onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); - onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); - onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); - this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); - } - _.mixin(Typeahead.prototype, { - _hacks: function hacks() { - var $input, $menu; - $input = this.input.$input || $("
"); - $menu = this.menu.$node || $("
"); - $input.on("blur.tt", function($e) { - var active, isActive, hasActive; - active = document.activeElement; - isActive = $menu.is(active); - hasActive = $menu.has(active).length > 0; - if (_.isMsie() && (isActive || hasActive)) { - $e.preventDefault(); - $e.stopImmediatePropagation(); - _.defer(function() { - $input.focus(); - }); - } - }); - $menu.on("mousedown.tt", function($e) { - $e.preventDefault(); - }); - }, - _onSelectableClicked: function onSelectableClicked(type, $el) { - this.select($el); - }, - _onDatasetCleared: function onDatasetCleared() { - this._updateHint(); - }, - _onDatasetRendered: function onDatasetRendered(type, suggestions, async, dataset) { - this._updateHint(); - if (this.autoselect) { - var cursorClass = this.selectors.cursor.substr(1); - this.menu.$node.find(this.selectors.suggestion).first().addClass(cursorClass); - } - this.eventBus.trigger("render", suggestions, async, dataset); - }, - _onAsyncRequested: function onAsyncRequested(type, dataset, query) { - this.eventBus.trigger("asyncrequest", query, dataset); - }, - _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { - this.eventBus.trigger("asynccancel", query, dataset); - }, - _onAsyncReceived: function onAsyncReceived(type, dataset, query) { - this.eventBus.trigger("asyncreceive", query, dataset); - }, - _onFocused: function onFocused() { - this._minLengthMet() && this.menu.update(this.input.getQuery()); - }, - _onBlurred: function onBlurred() { - if (this.input.hasQueryChangedSinceLastFocus()) { - this.eventBus.trigger("change", this.input.getQuery()); - } - }, - _onEnterKeyed: function onEnterKeyed(type, $e) { - var $selectable; - if ($selectable = this.menu.getActiveSelectable()) { - if (this.select($selectable)) { - $e.preventDefault(); - $e.stopPropagation(); - } - } else if (this.autoselect) { - if (this.select(this.menu.getTopSelectable())) { - $e.preventDefault(); - $e.stopPropagation(); - } - } - }, - _onTabKeyed: function onTabKeyed(type, $e) { - var $selectable; - if ($selectable = this.menu.getActiveSelectable()) { - this.select($selectable) && $e.preventDefault(); - } else if (this.autoselect) { - if ($selectable = this.menu.getTopSelectable()) { - this.autocomplete($selectable) && $e.preventDefault(); - } - } - }, - _onEscKeyed: function onEscKeyed() { - this.close(); - }, - _onUpKeyed: function onUpKeyed() { - this.moveCursor(-1); - }, - _onDownKeyed: function onDownKeyed() { - this.moveCursor(+1); - }, - _onLeftKeyed: function onLeftKeyed() { - if (this.dir === "rtl" && this.input.isCursorAtEnd()) { - this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable()); - } - }, - _onRightKeyed: function onRightKeyed() { - if (this.dir === "ltr" && this.input.isCursorAtEnd()) { - this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable()); - } - }, - _onQueryChanged: function onQueryChanged(e, query) { - this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); - }, - _onWhitespaceChanged: function onWhitespaceChanged() { - this._updateHint(); - }, - _onLangDirChanged: function onLangDirChanged(e, dir) { - if (this.dir !== dir) { - this.dir = dir; - this.menu.setLanguageDirection(dir); - } - }, - _openIfActive: function openIfActive() { - this.isActive() && this.open(); - }, - _minLengthMet: function minLengthMet(query) { - query = _.isString(query) ? query : this.input.getQuery() || ""; - return query.length >= this.minLength; - }, - _updateHint: function updateHint() { - var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; - $selectable = this.menu.getTopSelectable(); - data = this.menu.getSelectableData($selectable); - val = this.input.getInputValue(); - if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { - query = Input.normalizeQuery(val); - escapedQuery = _.escapeRegExChars(query); - frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); - match = frontMatchRegEx.exec(data.val); - match && this.input.setHint(val + match[1]); - } else { - this.input.clearHint(); - } - }, - isEnabled: function isEnabled() { - return this.enabled; - }, - enable: function enable() { - this.enabled = true; - }, - disable: function disable() { - this.enabled = false; - }, - isActive: function isActive() { - return this.active; - }, - activate: function activate() { - if (this.isActive()) { - return true; - } else if (!this.isEnabled() || this.eventBus.before("active")) { - return false; - } else { - this.active = true; - this.eventBus.trigger("active"); - return true; - } - }, - deactivate: function deactivate() { - if (!this.isActive()) { - return true; - } else if (this.eventBus.before("idle")) { - return false; - } else { - this.active = false; - this.close(); - this.eventBus.trigger("idle"); - return true; - } - }, - isOpen: function isOpen() { - return this.menu.isOpen(); - }, - open: function open() { - if (!this.isOpen() && !this.eventBus.before("open")) { - this.input.setAriaExpanded(true); - this.menu.open(); - this._updateHint(); - this.eventBus.trigger("open"); - } - return this.isOpen(); - }, - close: function close() { - if (this.isOpen() && !this.eventBus.before("close")) { - this.input.setAriaExpanded(false); - this.menu.close(); - this.input.clearHint(); - this.input.resetInputValue(); - this.eventBus.trigger("close"); - } - return !this.isOpen(); - }, - setVal: function setVal(val) { - this.input.setQuery(_.toStr(val)); - }, - getVal: function getVal() { - return this.input.getQuery(); - }, - select: function select($selectable) { - var data = this.menu.getSelectableData($selectable); - if (data && !this.eventBus.before("select", data.obj, data.dataset)) { - this.input.setQuery(data.val, true); - this.eventBus.trigger("select", data.obj, data.dataset); - this.close(); - return true; - } - return false; - }, - autocomplete: function autocomplete($selectable) { - var query, data, isValid; - query = this.input.getQuery(); - data = this.menu.getSelectableData($selectable); - isValid = data && query !== data.val; - if (isValid && !this.eventBus.before("autocomplete", data.obj, data.dataset)) { - this.input.setQuery(data.val); - this.eventBus.trigger("autocomplete", data.obj, data.dataset); - return true; - } - return false; - }, - moveCursor: function moveCursor(delta) { - var query, $candidate, data, suggestion, datasetName, cancelMove, id; - query = this.input.getQuery(); - $candidate = this.menu.selectableRelativeToCursor(delta); - data = this.menu.getSelectableData($candidate); - suggestion = data ? data.obj : null; - datasetName = data ? data.dataset : null; - id = $candidate ? $candidate.attr("id") : null; - this.input.trigger("cursorchange", id); - cancelMove = this._minLengthMet() && this.menu.update(query); - if (!cancelMove && !this.eventBus.before("cursorchange", suggestion, datasetName)) { - this.menu.setCursor($candidate); - if (data) { - if (typeof data.val === "string") { - this.input.setInputValue(data.val); - } - } else { - this.input.resetInputValue(); - this._updateHint(); - } - this.eventBus.trigger("cursorchange", suggestion, datasetName); - return true; - } - return false; - }, - destroy: function destroy() { - this.input.destroy(); - this.menu.destroy(); - } - }); - return Typeahead; - function c(ctx) { - var methods = [].slice.call(arguments, 1); - return function() { - var args = [].slice.call(arguments); - _.each(methods, function(method) { - return ctx[method].apply(ctx, args); - }); - }; - } - }(); - (function() { - "use strict"; - var old, keys, methods; - old = $.fn.typeahead; - keys = { - www: "tt-www", - attrs: "tt-attrs", - typeahead: "tt-typeahead" - }; - methods = { - initialize: function initialize(o, datasets) { - var www; - datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); - o = o || {}; - www = WWW(o.classNames); - return this.each(attach); - function attach() { - var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, status, typeahead, MenuConstructor; - _.each(datasets, function(d) { - d.highlight = !!o.highlight; - }); - $input = $(this); - $wrapper = $(www.html.wrapper); - $hint = $elOrNull(o.hint); - $menu = $elOrNull(o.menu); - defaultHint = o.hint !== false && !$hint; - defaultMenu = o.menu !== false && !$menu; - defaultHint && ($hint = buildHintFromInput($input, www)); - defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); - $hint && $hint.val(""); - $input = prepInput($input, www); - if (defaultHint || defaultMenu) { - $wrapper.css(www.css.wrapper); - $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); - $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); - } - MenuConstructor = defaultMenu ? DefaultMenu : Menu; - eventBus = new EventBus({ - el: $input - }); - input = new Input({ - hint: $hint, - input: $input, - menu: $menu - }, www); - menu = new MenuConstructor({ - node: $menu, - datasets: datasets - }, www); - status = new Status({ - $input: $input, - menu: menu - }); - typeahead = new Typeahead({ - input: input, - menu: menu, - eventBus: eventBus, - minLength: o.minLength, - autoselect: o.autoselect - }, www); - $input.data(keys.www, www); - $input.data(keys.typeahead, typeahead); - } - }, - isEnabled: function isEnabled() { - var enabled; - ttEach(this.first(), function(t) { - enabled = t.isEnabled(); - }); - return enabled; - }, - enable: function enable() { - ttEach(this, function(t) { - t.enable(); - }); - return this; - }, - disable: function disable() { - ttEach(this, function(t) { - t.disable(); - }); - return this; - }, - isActive: function isActive() { - var active; - ttEach(this.first(), function(t) { - active = t.isActive(); - }); - return active; - }, - activate: function activate() { - ttEach(this, function(t) { - t.activate(); - }); - return this; - }, - deactivate: function deactivate() { - ttEach(this, function(t) { - t.deactivate(); - }); - return this; - }, - isOpen: function isOpen() { - var open; - ttEach(this.first(), function(t) { - open = t.isOpen(); - }); - return open; - }, - open: function open() { - ttEach(this, function(t) { - t.open(); - }); - return this; - }, - close: function close() { - ttEach(this, function(t) { - t.close(); - }); - return this; - }, - select: function select(el) { - var success = false, $el = $(el); - ttEach(this.first(), function(t) { - success = t.select($el); - }); - return success; - }, - autocomplete: function autocomplete(el) { - var success = false, $el = $(el); - ttEach(this.first(), function(t) { - success = t.autocomplete($el); - }); - return success; - }, - moveCursor: function moveCursoe(delta) { - var success = false; - ttEach(this.first(), function(t) { - success = t.moveCursor(delta); - }); - return success; - }, - val: function val(newVal) { - var query; - if (!arguments.length) { - ttEach(this.first(), function(t) { - query = t.getVal(); - }); - return query; - } else { - ttEach(this, function(t) { - t.setVal(_.toStr(newVal)); - }); - return this; - } - }, - destroy: function destroy() { - ttEach(this, function(typeahead, $input) { - revert($input); - typeahead.destroy(); - }); - return this; - } - }; - $.fn.typeahead = function(method) { - if (methods[method]) { - return methods[method].apply(this, [].slice.call(arguments, 1)); - } else { - return methods.initialize.apply(this, arguments); - } - }; - $.fn.typeahead.noConflict = function noConflict() { - $.fn.typeahead = old; - return this; - }; - function ttEach($els, fn) { - $els.each(function() { - var $input = $(this), typeahead; - (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); - }); - } - function buildHintFromInput($input, www) { - return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop({ - readonly: true, - required: false - }).removeAttr("id name placeholder").removeClass("required").attr({ - spellcheck: "false", - tabindex: -1 - }); - } - function prepInput($input, www) { - $input.data(keys.attrs, { - dir: $input.attr("dir"), - autocomplete: $input.attr("autocomplete"), - spellcheck: $input.attr("spellcheck"), - style: $input.attr("style") - }); - $input.addClass(www.classes.input).attr({ - spellcheck: false - }); - try { - !$input.attr("dir") && $input.attr("dir", "auto"); - } catch (e) {} - return $input; - } - function getBackgroundStyles($el) { - return { - backgroundAttachment: $el.css("background-attachment"), - backgroundClip: $el.css("background-clip"), - backgroundColor: $el.css("background-color"), - backgroundImage: $el.css("background-image"), - backgroundOrigin: $el.css("background-origin"), - backgroundPosition: $el.css("background-position"), - backgroundRepeat: $el.css("background-repeat"), - backgroundSize: $el.css("background-size") - }; - } - function revert($input) { - var www, $wrapper; - www = $input.data(keys.www); - $wrapper = $input.parent().filter(www.selectors.wrapper); - _.each($input.data(keys.attrs), function(val, key) { - _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); - }); - $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); - if ($wrapper.length) { - $input.detach().insertAfter($wrapper); - $wrapper.remove(); - } - } - function $elOrNull(obj) { - var isValid, $el; - isValid = _.isJQuery(obj) || _.isElement(obj); - $el = isValid ? $(obj).first() : []; - return $el.length ? $el : null; - } - })(); -}); \ No newline at end of file diff --git a/docs/docsets/.docset/Contents/Resources/Documents/protobufsupport.html b/docs/docsets/.docset/Contents/Resources/Documents/protobufsupport.html deleted file mode 100644 index 7c03174..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/protobufsupport.html +++ /dev/null @@ -1,514 +0,0 @@ - - - - ProtobufSupport Reference - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
- -

Protocol Buffer Compatibility

- -

BinaryCodable provides limited compatibility to Google Protocol Buffers. Certain Swift types can be encoded to protobuf compatible binary data, and vice versa. The standard binary format is similar to protobuf, but includes some deviations to support all Swift types and features. There are additional ProtobufEncoder and ProtobufDecoder types which change the encoding format to be protobuf-compatible, at the expense of errors for unsupported features.

- -

For a description of the Protocol Buffer format, see the official documentation.

- -

Important notes

- -
    -
  • Advanced protobuf features like message concatenation are not supported.
  • -
  • Unsupported features of Protobuf may cause the encoding to fail with a ProtobufEncodingError. Interoperability should be thoroughly checked through testing.
  • -
-

Usage

- -

The conversion process is equivalent to the BinaryEncoder and BinaryDecoder types.

-
import BinaryCodable
-
-

Encoding

- -

Construct an encoder when converting instances to binary data, and feed the message(s) into it:

-
let message: Message = ...
-
-let encoder = ProtobufEncoder()
-let data = try encoder.encode(message)
-
-

Decoding

- -

Decoding instances from binary data works much the same way:

-
let decoder = ProtobufDecoder()
-let message = try decoder.decode(Message.self, from: data)
-
- -

Alternatively, the type can be inferred:

-
let message: Message = try decoder.decode(from: data)
-
-

Errors

- -

It is possible for both encoding and decoding to fail. -All possible errors occuring during encoding produce BinaryEncodingError or ProtobufEncodingError errors, while unsuccessful decoding produces BinaryDecodingError or ProtobufDecodingError. -All are enums with several cases describing the nature of the error. -See the documentation of the types to learn more about the different error conditions.

-

Message definition

- -

Protobuf organizes data into messages, which are structures with keyed fields. Compatible Swift types must be similar, so either struct or class. Simple types like Int or Bool are not supported on the root level, and neither are Dictionary, Array or enum. It’s best to think of the proto definitions and construct Swift types in the same way. Let’s look at an example from the Protocol Buffer documentation:

-
message SearchRequest {
-    string query = 1;
-    int32 page_number = 2;
-    int32 result_per_page = 3;
-}
-
- -

The corresponding Swift definition would be:

-
struct SearchRequest: Codable {
-
-    var query: String
-
-    var pageNumber: Int32
-
-    var resultPerPage: Int32
-
-    enum CodingKeys: Int, CodingKey {
-        case query = 1
-        case pageNumber = 2
-        case resultPerPage = 3
-    }
-}
-
- -

The general structure of the messages is very similar, with the proto field numbers specified as integer coding keys.

-

Assigning integer keys

- -

The assignment of integer keys follow the same rules as for field numbers, just written out as an enum with RawValue == Int on the type conforming to CodingKey. The smallest field number you can specify is 1, and the largest is 2^29 - 1, or 536,870,911. Codable types without (or with invalid) integer keys can’t be encoded using ProtobufEncoder and will throw an error.

-

Scalar value types

- -

There are several scalar types defined for Protocol Buffers, which are the basic building blocks of messages. BinaryCodable provides Swift equivalents for each of them:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Protobuf primitiveSwift equivalentComment
doubleDoubleAlways 8 byte
floatFloatAlways 4 byte
int32Int32Uses variable-length encoding
int64Int64Uses variable-length encoding
uint32UInt32Uses variable-length encoding
uint64UInt64Uses variable-length encoding
sint32SignedInteger<Int32>Uses ZigZag encoding, see SignedInteger wrapper
sint64SignedInteger<Int64>Uses ZigZag encoding, see SignedInteger wrapper
fixed32FixedSize<UInt32>See FixedSize wrapper
fixed64FixedSize<UInt64>See FixedSize wrapper
sfixed32FixedSize<Int32>See FixedSize wrapper
sfixed64FixedSize<Int64>See FixedSize wrapper
boolBoolAlways 1 byte
stringStringEncoded using UTF-8
bytesDataEncoded as-is
messagestructNested messages are also supported.
repeatedArrayScalar values must always be packed (the proto3 default)
enumEnumSee Enums
oneofEnumSee OneOf Definition
- -

The Swift types Int8, UInt8, Int16, and UInt16 are not supported, and will result in an error.

- -

Note: Int and UInt values are always encoded as 64-bit numbers, despite the fact that they might be 32-bit values on some systems. Decoding a 64-bit value on a 32-bit system will result in an error.

-

Property wrappers

- -

The Protocol Buffer format provides several different encoding strategies for integers to minimize the binary size depending on the encoded values. By default, all integers are encoded using Base 128 Varints, but this can be changed using Swift PropertyWrappers. The following encoding options exist:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Swift typeVarint encodingZigZag EncodingFixed-size encoding
Int32Int32SignedInteger<Int32>FixedSize<Int32>
Int64Int64SignedInteger<Int64>FixedSize<Int64>
UInt32UInt32-FixedSize<UInt32>
UInt64UInt64-FixedSize<UInt64>
-

Fixed size integers

- -

While varints are efficient for small numbers, their encoding introduces a storage and computation penalty when the integers are often large, e.g. for random numbers. BinaryCodable provides the FixedSize wrapper, which forces integers to be encoded using their little-endian binary representations. This means that e.g. an Int32 is always encoded as 4 byte (instead of 1-5 bytes using Varint encoding). This makes 32-bit FixedSize types more efficient than Varint if values are often larger than 2^28 (2^56 for 64-bit types).

- -

Use the property wrapper within a Codable definition to enforce fixed-width encoding for a property:

-
struct MyStruct: Codable {
-
-    /// Always encoded as 4 bytes
-    @FixedSize 
-    var largeInteger: Int32
-}
-
- -

The FixedSize wrapper is available to all Varint types: Int, UInt, Int32, UInt32, Int64, and UInt64.

-

Signed integers

- -

Integers are by default encoded as Varint values, which is efficient while numbers are small and positive. For numbers which are mostly or also often negative, it is more efficient to store them using Zig-Zag encoding. BinaryCodable offers the SignedValue wrapper that can be applied to Int, Int32 and Int64 properties to increase the efficiency for negative values.

- -

Whenever your integers are expected to be negative, then you should apply the wrapper:

-
struct MyStruct: Codable {
-
-    /// More efficiently encodes negative numbers
-    @SignedValue 
-    var count: Int
-}
-
-

Enums

- -

Protocol Buffer enumerations are supported, with a few notable caveats. Here is the example from the official documentation:

-
message SearchRequest {
-
-    ...
-
-    enum Corpus {
-        UNIVERSAL = 0;
-        WEB = 1;
-        IMAGES = 2;
-        LOCAL = 3;
-        NEWS = 4;
-        PRODUCTS = 5;
-        VIDEO = 6;
-    }
-    Corpus corpus = 4;
-}
-
- -

The BinaryCodable Swift equivalent would be:

-
struct SearchRequest: Codable {
-
-    ...
-
-    enum Corpus: Int, Codable {
-        case universal = 0
-        case web = 1
-        case images = 2
-        case local = 3
-        case news = 4
-        case products = 5
-        case video = 6
-    }
-
-    var corpus: Corpus
-
-    enum CodingKeys: Int, CodingKey {
-        case corpus = 4
-    }
-}
-
- -

It should be noted that protobuf enums require a default key 0.

-

Oneof

- -

The protobuf feature Oneof can also be supported using a special enum definition. Given the protobuf definition (from here):

-
syntax = "proto3";
-message ExampleOneOf {
-   int32 field1 = 1;
-   oneof alternatives {
-       int64 id = 2;
-       string name = 3;
-   }
-}
-
- -

The corresponding Swift definition would be:

-
struct ExampleOneOf: Codable {
-
-    let field1: Int32
-
-    // The oneof field
-    let alternatives: Alternatives
-
-    // The OneOf definition
-    enum Alternatives: Codable, ProtobufOneOf {
-        case id(Int64)
-        case name(String)
-
-        // Field values, must not overlap with `ExampleOneOf.CodingKeys`
-        enum CodingKeys: Int, CodingKey {
-            case id = 2
-            case name = 3
-        }
-    }
-
-    enum CodingKeys: Int, CodingKey {
-        case field1 = 1
-        // The field id of the Oneof field is not used
-        case alternatives = 123456
-    }
- }
-
- -

Note that the Alternatives enum must conform to ProtobufOneOf, which changes the encoding to create compatibility with the Protobuf binary format.

- -

Important The ProtobufOneOf protocol must not be applied to any other types, or encoding/decoding will fail.

- -
-
- -
-
- - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/search.json b/docs/docsets/.docset/Contents/Resources/Documents/search.json deleted file mode 100644 index 913b7f7..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/search.json +++ /dev/null @@ -1 +0,0 @@ -{"Structs/SignedValue.html#/s:s27ExpressibleByIntegerLiteralP0cD4TypeQa":{"name":"IntegerLiteralType","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV07wrappedD0xvp":{"name":"wrappedValue","abstract":"

The value wrapped in the fixed-size container

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV07wrappedD0ACyxGx_tcfc":{"name":"init(wrappedValue:)","abstract":"

Wrap an integer value in a fixed-size container

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:s27ExpressibleByIntegerLiteralP07integerD0x0cD4TypeQz_tcfc":{"name":"init(integerLiteral:)","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:SL1loiySbx_xtFZ":{"name":"<(_:_:)","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV6encode2toys7Encoder_p_tKF":{"name":"encode(to:)","abstract":"

Encode the wrapped value transparently to the given encoder.

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV4fromACyxGs7Decoder_p_tKcfc":{"name":"init(from:)","abstract":"

Decode a wrapped value from a decoder.

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV4zeroACyxGvpZ":{"name":"zero","abstract":"

The zero value.

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV3maxACyxGvpZ":{"name":"max","abstract":"

The maximum representable integer in this type.

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV3minACyxGvpZ":{"name":"min","abstract":"

The minimum representable integer in this type.

","parent_name":"SignedValue"},"Structs/FixedSize.html#/s:s27ExpressibleByIntegerLiteralP0cD4TypeQa":{"name":"IntegerLiteralType","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV12wrappedValuexvp":{"name":"wrappedValue","abstract":"

The value wrapped in the fixed-size container

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV12wrappedValueACyxGx_tcfc":{"name":"init(wrappedValue:)","abstract":"

Wrap an integer value in a fixed-size container

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:s27ExpressibleByIntegerLiteralP07integerD0x0cD4TypeQz_tcfc":{"name":"init(integerLiteral:)","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:SL1loiySbx_xtFZ":{"name":"<(_:_:)","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV6encode2toys7Encoder_p_tKF":{"name":"encode(to:)","abstract":"

Encode the wrapped value transparently to the given encoder.

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV4fromACyxGs7Decoder_p_tKcfc":{"name":"init(from:)","abstract":"

Decode a wrapped value from a decoder.

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV3maxACyxGvpZ":{"name":"max","abstract":"

The maximum representable integer in this type.

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV3minACyxGvpZ":{"name":"min","abstract":"

The minimum representable integer in this type.

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV4zeroACyxGvpZ":{"name":"zero","abstract":"

The zero value.

","parent_name":"FixedSize"},"Structs/FixedSize.html":{"name":"FixedSize","abstract":"

A wrapper for integer values which ensures that values are encoded in binary format using a fixed size.

"},"Structs/SignedValue.html":{"name":"SignedValue","abstract":"

A wrapper for integers more efficient for negative values.

"},"Protocols/SignedValueCompatible.html#/s:13BinaryCodable21SignedValueCompatibleP17positiveProtoTypeSSvp":{"name":"positiveProtoType","parent_name":"SignedValueCompatible"},"Protocols/FixedSizeCompatible.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","abstract":"

The wire type of the type, which has a constant length

","parent_name":"FixedSizeCompatible"},"Protocols/FixedSizeCompatible.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","abstract":"

The protobuf type equivalent to the fixed size type

","parent_name":"FixedSizeCompatible"},"Protocols/FixedSizeCompatible.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","abstract":"

The value encoded as fixed size binary data

","parent_name":"FixedSizeCompatible"},"Protocols/FixedSizeCompatible.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","abstract":"

Decode the value from binary data.

","parent_name":"FixedSizeCompatible"},"Protocols.html#/s:13BinaryCodable13ProtobufOneOfP":{"name":"ProtobufOneOf","abstract":"

Add conformance to this protocol to enums which should be encoded as Protobuf Oneof values.

"},"Protocols/FixedSizeCompatible.html":{"name":"FixedSizeCompatible","abstract":"

An integer type which can be forced to use a fixed-length encoding instead of variable-length encoding.

"},"Protocols/SignedValueCompatible.html":{"name":"SignedValueCompatible","abstract":"

A signed integer which can be forced to use zig-zag encoding.

"},"Extensions/UInt64.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"UInt64"},"Extensions/UInt64.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"UInt64"},"Extensions/UInt64.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"UInt64"},"Extensions/UInt64.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"UInt64"},"Extensions/UInt32.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"UInt32"},"Extensions/UInt32.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"UInt32"},"Extensions/UInt32.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"UInt32"},"Extensions/UInt32.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"UInt32"},"Extensions/UInt.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"UInt"},"Extensions/UInt.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"UInt"},"Extensions/UInt.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"UInt"},"Extensions/UInt.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"UInt"},"Extensions/Int64.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"Int64"},"Extensions/Int64.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"Int64"},"Extensions/Int64.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"Int64"},"Extensions/Int64.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"Int64"},"Extensions/Int64.html#/s:s5Int64V13BinaryCodableE17positiveProtoTypeSSvp":{"name":"positiveProtoType","parent_name":"Int64"},"Extensions/Int32.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"Int32"},"Extensions/Int32.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"Int32"},"Extensions/Int32.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"Int32"},"Extensions/Int32.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"Int32"},"Extensions/Int32.html#/s:s5Int32V13BinaryCodableE17positiveProtoTypeSSvp":{"name":"positiveProtoType","parent_name":"Int32"},"Extensions/Int.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"Int"},"Extensions/Int.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"Int"},"Extensions/Int.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"Int"},"Extensions/Int.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"Int"},"Extensions/Int.html#/s:Si13BinaryCodableE17positiveProtoTypeSSvp":{"name":"positiveProtoType","parent_name":"Int"},"Extensions.html#/s:13BinaryCodable11DecodingKeyO":{"name":"DecodingKey"},"Extensions.html#/s:13BinaryCodable21MixedCodingKeyWrapperV":{"name":"MixedCodingKeyWrapper"},"Extensions.html#/s:13BinaryCodable15ProtoKeyWrapperV":{"name":"ProtoKeyWrapper"},"Extensions.html#/s:13BinaryCodable13IntKeyWrapperV":{"name":"IntKeyWrapper"},"Extensions.html#/s:Sb":{"name":"Bool"},"Extensions.html#/s:10Foundation4DataV":{"name":"Data"},"Extensions.html#/s:Sf":{"name":"Float"},"Extensions/Int.html":{"name":"Int"},"Extensions/Int32.html":{"name":"Int32"},"Extensions/Int64.html":{"name":"Int64"},"Extensions.html#/s:SS":{"name":"String"},"Extensions/UInt.html":{"name":"UInt"},"Extensions/UInt32.html":{"name":"UInt32"},"Extensions/UInt64.html":{"name":"UInt64"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO36multipleValuesInSingleValueContaineryA2CmF":{"name":"multipleValuesInSingleValueContainer","abstract":"

A procedural error occuring during encoding.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO015noValueInSingleG9ContaineryA2CmF":{"name":"noValueInSingleValueContainer","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO21nilValuesNotSupportedyA2CmF":{"name":"nilValuesNotSupported","abstract":"

The encoded type contains optional values, which are not supported in the protocol buffer format.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO15unsupportedTypeyACSScACmF":{"name":"unsupportedType(_:)","abstract":"

The encoded type contains a basic type that is not supported.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO17superNotSupportedyA2CmF":{"name":"superNotSupported","abstract":"

Protocol buffers don’t support inheritance, so super can’t be encoded.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO17missingIntegerKeyyACSScACmF":{"name":"missingIntegerKey(_:)","abstract":"

The encoded type contains properties which don’t have an integer key.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO31multipleTypesInUnkeyedContaineryA2CmF":{"name":"multipleTypesInUnkeyedContainer","abstract":"

All values in unkeyed containers must have the same type.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO20integerKeyOutOfRangeyACSicACmF":{"name":"integerKeyOutOfRange(_:)","abstract":"

Field numbers must be positive integers not greater than 536870911 (2^29-1, or 0x1FFFFFFF)

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO26multipleContainersAccessedyA2CmF":{"name":"multipleContainersAccessed","abstract":"

Multiple calls to container<>(keyedBy:), unkeyedContainer(), or singleValueContainer() for an encoder.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO20noContainersAccessedyA2CmF":{"name":"noContainersAccessed","abstract":"

No calls to container<>(keyedBy:), unkeyedContainer(), or singleValueContainer() for an encoder.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO23rootIsNotKeyedContaineryA2CmF":{"name":"rootIsNotKeyedContainer","abstract":"

Protobuf requires an unkeyed container as the root node

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO13invalidAccessyACSScACmF":{"name":"invalidAccess(_:)","abstract":"

An unavailable encoding feature was accessed.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO29protobufDefinitionUnavailableyACSScACmF":{"name":"protobufDefinitionUnavailable(_:)","parent_name":"ProtobufEncodingError"},"Enums/ProtobufDecodingError.html#/s:13BinaryCodable21ProtobufDecodingErrorO23unexpectedDictionaryKeyyA2CmF":{"name":"unexpectedDictionaryKey","parent_name":"ProtobufDecodingError"},"Enums/ProtobufDecodingError.html#/s:13BinaryCodable21ProtobufDecodingErrorO17superNotSupportedyA2CmF":{"name":"superNotSupported","abstract":"

Protocol buffers don’t support inheritance, so super can’t be encoded.

","parent_name":"ProtobufDecodingError"},"Enums/ProtobufDecodingError.html#/s:13BinaryCodable21ProtobufDecodingErrorO15unsupportedTypeyACSScACmF":{"name":"unsupportedType(_:)","abstract":"

The encoded type contains a basic type that is not supported.

","parent_name":"ProtobufDecodingError"},"Enums/ProtobufDecodingError.html#/s:13BinaryCodable21ProtobufDecodingErrorO13invalidAccessyACSScACmF":{"name":"invalidAccess(_:)","abstract":"

A decoding feature was accessed which is not supported for protobuf encoding.

","parent_name":"ProtobufDecodingError"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO21variableLengthIntegeryA2CmF":{"name":"variableLengthInteger","abstract":"

An integer value encoded as a Base128 Varint.","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO4byteyA2CmF":{"name":"byte","abstract":"

The value is encoded as a single byte.

","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO8twoBytesyA2CmF":{"name":"twoBytes","abstract":"

The value is encoded as two bytes.

","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO14variableLengthyA2CmF":{"name":"variableLength","abstract":"

The value is encoded using first a length (as a UInt64 var-int) followed by the bytes.

","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO9fourBytesyA2CmF":{"name":"fourBytes","abstract":"

The value is encoded using four bytes.

","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO10eightBytesyA2CmF":{"name":"eightBytes","abstract":"

The value is encoded using eight bytes.

","parent_name":"DataType"},"Enums/DataType.html":{"name":"DataType","abstract":"

The data type specifying how a value is encoded on the wire.

"},"Enums/ProtobufDecodingError.html":{"name":"ProtobufDecodingError","abstract":"

An error produced while decoding binary data.

"},"Enums/ProtobufEncodingError.html":{"name":"ProtobufEncodingError","abstract":"

An error thrown when encoding a value using ProtobufEncoder.

"},"Classes/ProtobufEncoder.html#/s:13BinaryCodable15ProtobufEncoderC8userInfoSDys010CodingUserF3KeyVypGvp":{"name":"userInfo","abstract":"

Any contextual information set by the user for encoding.

","parent_name":"ProtobufEncoder"},"Classes/ProtobufEncoder.html#/s:13BinaryCodable15ProtobufEncoderCACycfc":{"name":"init()","abstract":"

Create a new binary encoder.

","parent_name":"ProtobufEncoder"},"Classes/ProtobufEncoder.html#/s:13BinaryCodable15ProtobufEncoderC6encodey10Foundation4DataVxKSERzlF":{"name":"encode(_:)","abstract":"

Encode a value to binary data.

","parent_name":"ProtobufEncoder"},"Classes/ProtobufEncoder.html#/s:13BinaryCodable15ProtobufEncoderC6encodey10Foundation4DataVxKSERzlFZ":{"name":"encode(_:)","abstract":"

Encode a single value to binary data using a default encoder.

","parent_name":"ProtobufEncoder"},"Classes/ProtobufDecoder.html#/s:13BinaryCodable15ProtobufDecoderC8userInfoSDys010CodingUserF3KeyVypGvp":{"name":"userInfo","abstract":"

Any contextual information set by the user for decoding.

","parent_name":"ProtobufDecoder"},"Classes/ProtobufDecoder.html#/s:13BinaryCodable15ProtobufDecoderCACycfc":{"name":"init()","abstract":"

Create a new binary encoder.

","parent_name":"ProtobufDecoder"},"Classes/ProtobufDecoder.html#/s:13BinaryCodable15ProtobufDecoderC6decode_4fromxxm_10Foundation4DataVtKSeRzlF":{"name":"decode(_:from:)","abstract":"

Decode a type from binary data.

","parent_name":"ProtobufDecoder"},"Classes/ProtobufDecoder.html#/s:13BinaryCodable15ProtobufDecoderC6decode_4fromxxm_10Foundation4DataVtKSeRzlFZ":{"name":"decode(_:from:)","abstract":"

Decode a single value from binary data using a default decoder.

","parent_name":"ProtobufDecoder"},"Classes/BinaryStreamEncoder.html#/s:13BinaryCodable0A13StreamEncoderC7encoderACyxGAA0aD0C_tcfc":{"name":"init(encoder:)","abstract":"

Create a new stream encoder.

","parent_name":"BinaryStreamEncoder"},"Classes/BinaryStreamEncoder.html#/s:13BinaryCodable0A13StreamEncoderC6encodey10Foundation4DataVxKF":{"name":"encode(_:)","abstract":"

Encode an element for the data stream.

","parent_name":"BinaryStreamEncoder"},"Classes/BinaryStreamEncoder.html#/s:13BinaryCodable0A13StreamEncoderC6encode10contentsOf10Foundation4DataVqd___tK7ElementQyd__RszSTRd__lF":{"name":"encode(contentsOf:)","abstract":"

Encode a sequence of elements.

","parent_name":"BinaryStreamEncoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC7decoderACyxGAA0aD0C_tcfc":{"name":"init(decoder:)","abstract":"

Create a stream decoder.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC3addyy10Foundation4DataVF":{"name":"add(_:)","abstract":"

Add new data to the internal buffer without attempting to decode elements.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC12discardBytesyS2iF":{"name":"discardBytes(_:)","abstract":"

Discard bytes from the currently remaining bytes.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC6decode_25returnElementsBeforeErrorSayxG10Foundation4DataV_SbtKF":{"name":"decode(_:returnElementsBeforeError:)","abstract":"

Read elements from the stream until no more bytes are available.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC24decodeElementsUntilErrorSayxG8elements_s0H0_pSg5errortyF":{"name":"decodeElementsUntilError()","abstract":"

Read elements from the stream until an error occurs.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC14decodeElementsSayxGyKF":{"name":"decodeElements()","abstract":"

Read elements until no more data is available.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC13decodeElementxSgyKF":{"name":"decodeElement()","abstract":"

Attempt to decode a single element from the stream.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryFileEncoder.html#/s:13BinaryCodable0A11FileEncoderC6fileAt7encoderACyxG10Foundation3URLV_AA0aD0CtKcfc":{"name":"init(fileAt:encoder:)","abstract":"

Create a new file encoder.

","parent_name":"BinaryFileEncoder"},"Classes/BinaryFileEncoder.html#/s:13BinaryCodable0A11FileEncoderC5closeyyKF":{"name":"close()","abstract":"

Close the file.

","parent_name":"BinaryFileEncoder"},"Classes/BinaryFileEncoder.html#/s:13BinaryCodable0A11FileEncoderC5writeyyxKF":{"name":"write(_:)","abstract":"

Write a single element to the file.

","parent_name":"BinaryFileEncoder"},"Classes/BinaryFileEncoder.html#/s:13BinaryCodable0A11FileEncoderC5write10contentsOfyqd___tK7ElementQyd__RszSTRd__lF":{"name":"write(contentsOf:)","abstract":"

Write a sequence of elements to the file.

","parent_name":"BinaryFileEncoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC6fileAt7decoderACyxG10Foundation3URLV_AA0aD0CtKcfc":{"name":"init(fileAt:decoder:)","abstract":"

Create a file decoder.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC5closeyyKF":{"name":"close()","abstract":"

Close the file.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC4readyyyxKXEKF":{"name":"read(_:)","abstract":"

Read all elements in the file, and handle each element using a closure.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC7readAllSayxGyKF":{"name":"readAll()","abstract":"

Read all elements at once.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC17readAllUntilErrorSayxGyF":{"name":"readAllUntilError()","abstract":"

Read all elements at once, and ignore errors.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC11readElementxSgyKF":{"name":"readElement()","abstract":"

Read a single elements from the current position in the file.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC22sortKeysDuringEncodingSbvp":{"name":"sortKeysDuringEncoding","abstract":"

Sort keyed data in the binary representation.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC38prependNilIndexSetForUnkeyedContainersSbvp":{"name":"prependNilIndexSetForUnkeyedContainers","abstract":"

Add a set of indices for nil values in unkeyed containers.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC8userInfoSDys010CodingUserE3KeyVypGvp":{"name":"userInfo","abstract":"

Any contextual information set by the user for encoding.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderCACycfc":{"name":"init()","abstract":"

Create a new binary encoder.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC6encodey10Foundation4DataVSE_pKF":{"name":"encode(_:)","abstract":"

Encode a value to binary data.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC6encodey10Foundation4DataVSE_pKFZ":{"name":"encode(_:)","abstract":"

Encode a single value to binary data using a default encoder.

","parent_name":"BinaryEncoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderC8userInfoSDys010CodingUserE3KeyVypGvp":{"name":"userInfo","abstract":"

Any contextual information set by the user for decoding.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderC39containsNilIndexSetForUnkeyedContainersSbvp":{"name":"containsNilIndexSetForUnkeyedContainers","abstract":"

Assumes that unkeyed containers are encoded using a set of indices for nil values.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderCACycfc":{"name":"init()","abstract":"

Create a new binary encoder.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderC6decode_4fromxxm_10Foundation4DataVtKSeRzlF":{"name":"decode(_:from:)","abstract":"

Decode a type from binary data.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderC6decode_4fromxxm_10Foundation4DataVtKSeRzlFZ":{"name":"decode(_:from:)","abstract":"

Decode a single value from binary data using a default decoder.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html":{"name":"BinaryDecoder","abstract":"

An encoder to convert binary data back to Codable objects.

"},"Classes/BinaryEncoder.html":{"name":"BinaryEncoder","abstract":"

An encoder to convert Codable objects to binary data.

"},"Classes/BinaryFileDecoder.html":{"name":"BinaryFileDecoder","abstract":"

Read elements from a binary file.

"},"Classes/BinaryFileEncoder.html":{"name":"BinaryFileEncoder","abstract":"

Encode a stream of elements to a binary file.

"},"Classes/BinaryStreamDecoder.html":{"name":"BinaryStreamDecoder","abstract":"

Decode elements from a byte stream.

"},"Classes/BinaryStreamEncoder.html":{"name":"BinaryStreamEncoder","abstract":"

Encode elements sequentially into a binary data stream.

"},"Classes/ProtobufDecoder.html":{"name":"ProtobufDecoder","abstract":"

An encoder to convert protobuf binary data back to Codable objects.

"},"Classes/ProtobufEncoder.html":{"name":"ProtobufEncoder","abstract":"

An encoder to convert Codable objects to protobuf binary data.

"},"binaryformat.html":{"name":"BinaryFormat"},"protobufsupport.html":{"name":"ProtobufSupport"},"Guides.html":{"name":"Guides","abstract":"

The following guides are available globally.

"},"Classes.html":{"name":"Classes","abstract":"

The following classes are available globally.

"},"Enums.html":{"name":"Enumerations","abstract":"

The following enumerations are available globally.

"},"Extensions.html":{"name":"Extensions","abstract":"

The following extensions are available globally.

"},"Protocols.html":{"name":"Protocols","abstract":"

The following protocols are available globally.

"},"Structs.html":{"name":"Structures","abstract":"

The following structures are available globally.

"}} \ No newline at end of file diff --git a/docs/docsets/.docset/Contents/Resources/Documents/undocumented.json b/docs/docsets/.docset/Contents/Resources/Documents/undocumented.json deleted file mode 100644 index da2d01c..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/undocumented.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "warnings": [ - - ], - "source_directory": "/Users/ch/Projects/BinaryCodable" -} \ No newline at end of file diff --git a/docs/docsets/.docset/Contents/Resources/docSet.dsidx b/docs/docsets/.docset/Contents/Resources/docSet.dsidx deleted file mode 100644 index 067b9cd..0000000 Binary files a/docs/docsets/.docset/Contents/Resources/docSet.dsidx and /dev/null differ diff --git a/docs/docsets/.tgz b/docs/docsets/.tgz deleted file mode 100644 index df35d96..0000000 Binary files a/docs/docsets/.tgz and /dev/null differ diff --git a/docs/img/carat.png b/docs/img/carat.png deleted file mode 100755 index 29d2f7f..0000000 Binary files a/docs/img/carat.png and /dev/null differ diff --git a/docs/img/dash.png b/docs/img/dash.png deleted file mode 100755 index 6f694c7..0000000 Binary files a/docs/img/dash.png and /dev/null differ diff --git a/docs/img/gh.png b/docs/img/gh.png deleted file mode 100755 index 628da97..0000000 Binary files a/docs/img/gh.png and /dev/null differ diff --git a/docs/img/spinner.gif b/docs/img/spinner.gif deleted file mode 100644 index e3038d0..0000000 Binary files a/docs/img/spinner.gif and /dev/null differ diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index bb6e157..0000000 --- a/docs/index.html +++ /dev/null @@ -1,445 +0,0 @@ - - - - Reference - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
- -

- BinaryCodable -

- -

- - - - - - - - -

- -

This package provides convenient encoding and decoding to/from binary data for all Swift Codable types. It also provides limited cross-compatibility to Google Protocol Buffers.

-

Use cases

- -

There are only few encoders and decoders available for Swift’s Codable format, and Apple provides a JSONEncoder and a PropertyListEncoder for basic encoding. While these can cover some use cases (especially when interacting with Web Content through JSON), they lack encoding efficiency when designing APIs within an ecosystem. JSON, for example, is notoriously inefficient when it comes to binary data.

- -

One very popular alternative for binary data are Google’s Protocol Buffers, which offer broad support across different platforms and programming languages. But they don’t support Swift’s Codable protocol, and thus require manual message definitions, the Protobuf compiler, and a lot of copying between data structures during encoding and decoding.

- -

So if you’re looking for a decently efficient binary encoder in a pure Swift project, then BinaryCodable may be right for you. Simply make your structs (or classes!) conform to Codable, and BinaryCodable does the rest!

- -

The message format is similar to that of Protocol Buffers (with some additions to support more types). It is possible to create limited compatibility between the two formats to exchange data with systems that don’t support Swift.

-

Alternatives

-

Protocol Buffers

- -

Already mentioned above

-

CBORCoding

- -

If you’re looking for a Codable-compatible alternative which is also available on other platforms, with a well-defined spec. It appears to have nearly the same encoding efficiency as BinaryCodable.

-

PotentCodables

- -

Also offers CBOR encoding, plus a bunch of other things related to Codable.

-

Swift BSON

- -

Encoding according to the BSON specification. Less efficient binary represenation than Protocol Buffers and BinaryCodable, but mature. Used for MongoDB.

-

Installation

-

Swift Package Manager

- -

Simply include in your Package.swift:

-
dependencies: [
-    .package(
-        name: "BinaryCodable", 
-        url: "https://github.com/christophhagen/BinaryCodable", 
-        from: "1.0.0")
-],
-targets: [
-    .target(name: "MyTarget", dependencies: [
-        .product(name: "BinaryCodable", package: "BinaryCodable")
-    ])
-]
-
-

Xcode project

- -

Select your Project, navigate to the Package Dependencies tab, and add https://github.com/christophhagen/BinaryCodable using the + button.

-

Usage

- -

Let’s assume a message definition:

-
struct Message: Codable {
-
-    var sender: String
-
-    var isRead: Bool
-
-    var unreadCount: Int
-}
-
- -

Simply import the module where you need to encode or decode a message:

-
import BinaryCodable
-
-

Encoding

- -

Construct an encoder when converting instances to binary data, and feed the message(s) into it:

-
let message: Message = ...
-
-let encoder = BinaryEncoder()
-let data = try encoder.encode(message)
-
- -

It’s also possible to encode single values, arrays, optionals, sets, enums, dictionaries, and more, so long as they conform to Codable.

-

Decoding

- -

Decoding instances from binary data works much the same way:

-
let decoder = BinaryDecoder()
-let message = try decoder.decode(Message.self, from: data)
-
- -

Alternatively, the type can be inferred:

-
let message: Message = try decoder.decode(from: data)
-
-

Errors

- -

It is possible for both encoding and decoding to fail. -All possible errors occuring during encoding produce EncodingError errors, while unsuccessful decoding produces DecodingErrors. -Both are the default Errors provided by Swift, supplied with information describing the nature of the error. -See the documentation of the types to learn more about the different error conditions.

-

Unsupported features

- -

It is currently not supported to call func encodeNil() on SingleValueEncodingContainer for custom implementations of func encode(to:). -Future versions may include a special setting to enforce compatibility with this option.

-

Handling corrupted data

- -

The binary format provides no provisions to detect data corruption, and various errors can occur as the result of added, changed, or missing bytes and bits. -Additional external measures (checksums, error-correcting codes, …) should be applied if there is an increased risk of data corruption.

- -

As an example, consider the simple encoding of a String inside a struct, which consists of a key followed by the length of the string in bytes, and the string content. -The length of the string is encoded using variable-length encoding, so a single bit flip (in the MSB of the length byte) could result in a very large length being decoded, causing the decoder to wait for a very large number of bytes to decode the string. -This simple error would cause much data to be skipped, potentially corrupting the data stream indefinitely. -At the same time, it is not possible to determine with certainty where the error occured, making error recovery difficult without additional information about boundaries between elements.

- -

The decoding errors provided by the library are therefore only hints about error likely occuring from non-conformance to the binary format or version incompatibility, which are not necessarily the true causes of the failures when data corruption is present.

-

Coding Keys

- -

The Codable protocol uses CodingKey definitions to identify properties of instances. By default, coding keys are generated using the string values of the property names.

- -

Similar to JSON encoding, BinaryCodable can embed the property names in the encoded data.

- -

Unlike JSON (which is human-readable), the binary representation produced by BinaryCodable is intended for cases when efficient encoding is important. Codable allows the use of integer keys for each property, which significantly increases encoding efficiency. You can specify integer keys by adding an Int enum conforming to the CodingKey protocol to the Codable type:

-
struct Message: Codable {
-
-    var sender: String
-
-    var isRead: Bool
-
-    var unreadCount: Int
-
-    // Assign an integer to each property
-    enum CodingKeys: Int, CodingKey {
-        case sender = 1
-        case isRead = 2
-        case unreadCount = 3
-    }
-}
-
- -

The enum must have a raw value of either Int or String, and the cases must match the property names within the type (it is possible to omit keys for properties which should not be encoded).

- -

Using integer keys can significantly decrease the binary size, especially for long property names. Additionally, integer keys can be useful when intending to store the binary data persistently. Changes to property names can be performed in the code without breaking the decoding of older data (although this can also be achieved with custom String keys).

- -

Notes:

- -
    -
  • Small, positive integer keys produce the smallest binary sizes.
  • -
  • The 0 integer key shouldn’t be used, since it is also used internally when encoding super.
  • -
  • Negative values for integer keys are not recommended (but possible). Since the keys are encoded as Varint, they are very inefficient for negative numbers.
  • -
  • The allowed range for integer keys is from -576460752303423488 (-2^59, inclusive) to 576460752303423487 (2^59-1, inclusive). Values outside of these bounds will cause a fatalError crash.
  • -
-

Property wrappers

-

Fixed size integers

- -

While varints are efficient for small numbers, their encoding introduces a storage and computation penalty when the integers are often large, e.g. for random numbers. BinaryCodable provides the FixedSize wrapper, which forces integers to be encoded using their little-endian binary representations. This means that e.g. an Int32 is always encoded as 4 byte (instead of 1-5 bytes using Varint encoding). This makes 32-bit FixedSize types more efficient than Varint if values are often larger than 2^28 (2^56 for 64-bit types).

- -

Use the property wrapper within a Codable definition to enforce fixed-width encoding for a property:

-
 struct MyStruct: Codable {
-
-     /// Always encoded as 4 bytes
-     @FixedSize 
-     var largeInteger: Int32
- }
-
- -

The FixedSize wrapper is available to all Varint types: Int, UInt, Int32, UInt32, Int64, and UInt64.

- -

#### Other property wrappers

- -

There is an additional SignedValue wrapper, which is only useful when encoding in protobuf-compatible format.

-

Options

-

Sorting keys

- -

The BinaryEncoder provides the sortKeysDuringEncoding option, which forces fields in “keyed” containers, such as struct properties (and some dictionaries), to be sorted in the binary data. This sorting is done by using either the integer keys (if defined), or the property names. Dictionaries with Int or String keys are also sorted.

- -

Sorting the binary data does not influence decoding, but introduces a computation penalty during encoding. It should therefore only be used if the binary data must be consistent across multiple invocations.

- -

Note: The sortKeysDuringEncoding option does not neccessarily guarantee deterministic binary data, and should be used with care.

-

Encoding optionals in arrays

- -

Sequences of Optional values (like arrays, sets, …) are normally encoded one additional byte to indicate following value (0x01) or a nil value (0x00). -This works well for all compiler-generated conformances to Codable. -For custom implementations of func encode(to: Encoder) and init(from: Decoder), the encodeNil() is not supported on UnkeyedEncodingContainers. -If you must use this option, then it’s necessary to enable the prependNilIndexSetForUnkeyedContainers option on both BinaryEncoder and BinaryDecoder. -Optional values are then encoded using a nil index set. -The index of each nil element in the sequence is recorded, and only non-nil values are encoded. -The indices of nil elements are then prepended to the data as an array of integers. -During decoding, this index set is checked to place nil values between the non-nil elements at the appropriate indices.

-

Stream encoding and decoding

- -

The library provides the option to perform encoding and decoding of continuous streams, such as when writing sequences of elements to a file, or when transmitting data over a network. -This functionality can be used through BinaryStreamEncoder and BinaryStreamDecoder, causing the encoder to embed additional information into the data to allow continuous decoding (mostly length information). -Encoding and decoding is always done with sequences of one specific type, since multiple types in one stream could not be distinguished from one another.

- -

Encoding of a stream works similarly to normal encoding:

-
let encoder = BinaryStreamEncoder<Int>()
-let chunk1 = try encoder.encode(1)
-let chunk2 = try encoder.encode(contentsOf: [2,3])
-...
-
-let data = chunk1 + chunk2 + ...
-
- -

Decoding of the individual chunks, with the decoder returning all elements which can be decoded using the currently available data.

-
let decoder = BinaryStreamDecoder<Int>()
-let decoded1 = try decoder.decode(chunk1)
-print(decoded1) // [1]
-
-let decoded2 = try decoder.decode(chunk2)
-print(decoded2) // [2,3]
-
- -

The decoder has an internal buffer, so incomplete data can be inserted into the decoder as it becomes available. The output of decode(_ data:) will be empty until the next complete element is processed.

-

File encoding and decoding

- -

Writing data streams to files is a common use case, so the library also provides wrappers around BinaryStreamEncoder and BinaryStreamDecoder to perform these tasks. -The BinaryFileEncoder can be used to sequentially write elements to a file:

-
let encoder = BinaryFileEncoder<DataElement>(fileAt: url)
-try encoder.write(element1)
-try encoder.write(element2)
-...
-try encoder.close() // Close the file
-
- -

Elements will always be appended to the end of file, so existing files can be updated with additional data.

- -

Decoding works in a similar way, except with a callback to handle each element as it is decoded:

-
let decoder = BinaryFileDecoder<DataElement>(fileAt: url)
-try decoder.read { element in
-    // Process each element
-}
-
- -

There is also the possibility to read all elements at once using readAll(), or to read only one element at a time (readElement()).

-

Protocol Buffer compatibility

- -

Achieving Protocol Buffer compatibility is described in ProtobufSupport.md.

-

Binary format

- -

To learn more about the encoding format, see BinaryFormat.md.

-

Tests

- -

The library comes with an extensive test suite, which checks that encoding works correctly for many cases. These tests can be executed using swift test from the package root, or when opening the package using Xcode.

-

License

- -

MIT. See License.md

-

Roadmap

-

Generate protobuf definitions

- -

It should be possible to generate a string containing a working Protobuf definition for any type that is determined to be Protobuf compatible.

-

Speed

- -

Increasing the speed of the encoding and decoding process is not a huge priority at the moment. -If you have any pointers on how to improve the performance further, feel free to contribute.

-

Contributing

- -

Users of the library are encouraged to contribute to this repository.

-

Feature suggestions

- -

Please file an issue with a description of the feature you’re missing. Check other open and closed issues for similar suggestions and comment on them before creating a new issue.

-

Bug reporting

- -

File an issue with a clear description of the problem. Please include message definitions and other data where possible so that the error can be reproduced.

-

Documentation

- -

If you would like to extend the documentation of this library, or translate the documentation into other languages, please also open an issue, and I’ll contact you for further discussions.

- -
-
- -
-
- - diff --git a/docs/js/jazzy.js b/docs/js/jazzy.js deleted file mode 100755 index 1984416..0000000 --- a/docs/js/jazzy.js +++ /dev/null @@ -1,74 +0,0 @@ -// Jazzy - https://github.com/realm/jazzy -// Copyright Realm Inc. -// SPDX-License-Identifier: MIT - -window.jazzy = {'docset': false} -if (typeof window.dash != 'undefined') { - document.documentElement.className += ' dash' - window.jazzy.docset = true -} -if (navigator.userAgent.match(/xcode/i)) { - document.documentElement.className += ' xcode' - window.jazzy.docset = true -} - -function toggleItem($link, $content) { - var animationDuration = 300; - $link.toggleClass('token-open'); - $content.slideToggle(animationDuration); -} - -function itemLinkToContent($link) { - return $link.parent().parent().next(); -} - -// On doc load + hash-change, open any targetted item -function openCurrentItemIfClosed() { - if (window.jazzy.docset) { - return; - } - var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); - $content = itemLinkToContent($link); - if ($content.is(':hidden')) { - toggleItem($link, $content); - } -} - -$(openCurrentItemIfClosed); -$(window).on('hashchange', openCurrentItemIfClosed); - -// On item link ('token') click, toggle its discussion -$('.token').on('click', function(event) { - if (window.jazzy.docset) { - return; - } - var $link = $(this); - toggleItem($link, itemLinkToContent($link)); - - // Keeps the document from jumping to the hash. - var href = $link.attr('href'); - if (history.pushState) { - history.pushState({}, '', href); - } else { - location.hash = href; - } - event.preventDefault(); -}); - -// Clicks on links to the current, closed, item need to open the item -$("a:not('.token')").on('click', function() { - if (location == this.href) { - openCurrentItemIfClosed(); - } -}); - -// KaTeX rendering -if ("katex" in window) { - $($('.math').each( (_, element) => { - katex.render(element.textContent, element, { - displayMode: $(element).hasClass('m-block'), - throwOnError: false, - trust: true - }); - })) -} diff --git a/docs/js/jazzy.search.js b/docs/js/jazzy.search.js deleted file mode 100644 index 359cdbb..0000000 --- a/docs/js/jazzy.search.js +++ /dev/null @@ -1,74 +0,0 @@ -// Jazzy - https://github.com/realm/jazzy -// Copyright Realm Inc. -// SPDX-License-Identifier: MIT - -$(function(){ - var $typeahead = $('[data-typeahead]'); - var $form = $typeahead.parents('form'); - var searchURL = $form.attr('action'); - - function displayTemplate(result) { - return result.name; - } - - function suggestionTemplate(result) { - var t = '
'; - t += '' + result.name + ''; - if (result.parent_name) { - t += '' + result.parent_name + ''; - } - t += '
'; - return t; - } - - $typeahead.one('focus', function() { - $form.addClass('loading'); - - $.getJSON(searchURL).then(function(searchData) { - const searchIndex = lunr(function() { - this.ref('url'); - this.field('name'); - this.field('abstract'); - for (const [url, doc] of Object.entries(searchData)) { - this.add({url: url, name: doc.name, abstract: doc.abstract}); - } - }); - - $typeahead.typeahead( - { - highlight: true, - minLength: 3, - autoselect: true - }, - { - limit: 10, - display: displayTemplate, - templates: { suggestion: suggestionTemplate }, - source: function(query, sync) { - const lcSearch = query.toLowerCase(); - const results = searchIndex.query(function(q) { - q.term(lcSearch, { boost: 100 }); - q.term(lcSearch, { - boost: 10, - wildcard: lunr.Query.wildcard.TRAILING - }); - }).map(function(result) { - var doc = searchData[result.ref]; - doc.url = result.ref; - return doc; - }); - sync(results); - } - } - ); - $form.removeClass('loading'); - $typeahead.trigger('focus'); - }); - }); - - var baseURL = searchURL.slice(0, -"search.json".length); - - $typeahead.on('typeahead:select', function(e, result) { - window.location = baseURL + result.url; - }); -}); diff --git a/docs/js/jquery.min.js b/docs/js/jquery.min.js deleted file mode 100644 index 7f37b5d..0000000 --- a/docs/js/jquery.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 00){var c=e.utils.clone(r)||{};c.position=[a,l],c.index=s.length,s.push(new e.Token(i.slice(a,o),c))}a=o+1}}return s},e.tokenizer.separator=/[\s\-]+/,e.Pipeline=function(){this._stack=[]},e.Pipeline.registeredFunctions=Object.create(null),e.Pipeline.registerFunction=function(t,r){r in this.registeredFunctions&&e.utils.warn("Overwriting existing registered function: "+r),t.label=r,e.Pipeline.registeredFunctions[t.label]=t},e.Pipeline.warnIfFunctionNotRegistered=function(t){var r=t.label&&t.label in this.registeredFunctions;r||e.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",t)},e.Pipeline.load=function(t){var r=new e.Pipeline;return t.forEach(function(t){var i=e.Pipeline.registeredFunctions[t];if(!i)throw new Error("Cannot load unregistered function: "+t);r.add(i)}),r},e.Pipeline.prototype.add=function(){var t=Array.prototype.slice.call(arguments);t.forEach(function(t){e.Pipeline.warnIfFunctionNotRegistered(t),this._stack.push(t)},this)},e.Pipeline.prototype.after=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,r)},e.Pipeline.prototype.before=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");this._stack.splice(i,0,r)},e.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);t!=-1&&this._stack.splice(t,1)},e.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r1&&(se&&(r=n),s!=e);)i=r-t,n=t+Math.floor(i/2),s=this.elements[2*n];return s==e?2*n:s>e?2*n:sa?l+=2:o==a&&(t+=r[u+1]*i[l+1],u+=2,l+=2);return t},e.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},e.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t0){var o,a=s.str.charAt(0);a in s.node.edges?o=s.node.edges[a]:(o=new e.TokenSet,s.node.edges[a]=o),1==s.str.length&&(o["final"]=!0),n.push({node:o,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(0!=s.editsRemaining){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new e.TokenSet;s.node.edges["*"]=u}if(0==s.str.length&&(u["final"]=!0),n.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&n.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),1==s.str.length&&(s.node["final"]=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new e.TokenSet;s.node.edges["*"]=l}1==s.str.length&&(l["final"]=!0),n.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var c,h=s.str.charAt(0),d=s.str.charAt(1);d in s.node.edges?c=s.node.edges[d]:(c=new e.TokenSet,s.node.edges[d]=c),1==s.str.length&&(c["final"]=!0),n.push({node:c,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return i},e.TokenSet.fromString=function(t){for(var r=new e.TokenSet,i=r,n=0,s=t.length;n=e;t--){var r=this.uncheckedNodes[t],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r["char"]]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}},e.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},e.Index.prototype.search=function(t){return this.query(function(r){var i=new e.QueryParser(t,r);i.parse()})},e.Index.prototype.query=function(t){for(var r=new e.Query(this.fields),i=Object.create(null),n=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},e.Builder.prototype.k1=function(e){this._k1=e},e.Builder.prototype.add=function(t,r){var i=t[this._ref],n=Object.keys(this._fields);this._documents[i]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return e.QueryLexer.EOS;var t=this.str.charAt(this.pos);return this.pos+=1,t},e.QueryLexer.prototype.width=function(){return this.pos-this.start},e.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},e.QueryLexer.prototype.backup=function(){this.pos-=1},e.QueryLexer.prototype.acceptDigitRun=function(){var t,r;do t=this.next(),r=t.charCodeAt(0);while(r>47&&r<58);t!=e.QueryLexer.EOS&&this.backup()},e.QueryLexer.prototype.more=function(){return this.pos1&&(t.backup(),t.emit(e.QueryLexer.TERM)),t.ignore(),t.more())return e.QueryLexer.lexText},e.QueryLexer.lexEditDistance=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.EDIT_DISTANCE),e.QueryLexer.lexText},e.QueryLexer.lexBoost=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.BOOST),e.QueryLexer.lexText},e.QueryLexer.lexEOS=function(t){t.width()>0&&t.emit(e.QueryLexer.TERM)},e.QueryLexer.termSeparator=e.tokenizer.separator,e.QueryLexer.lexText=function(t){for(;;){var r=t.next();if(r==e.QueryLexer.EOS)return e.QueryLexer.lexEOS;if(92!=r.charCodeAt(0)){if(":"==r)return e.QueryLexer.lexField;if("~"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexEditDistance;if("^"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexBoost;if("+"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if("-"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if(r.match(e.QueryLexer.termSeparator))return e.QueryLexer.lexTerm}else t.escapeCharacter()}},e.QueryParser=function(t,r){this.lexer=new e.QueryLexer(t),this.query=r,this.currentClause={},this.lexemeIdx=0},e.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var t=e.QueryParser.parseClause;t;)t=t(this);return this.query},e.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},e.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},e.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},e.QueryParser.parseClause=function(t){var r=t.peekLexeme();if(void 0!=r)switch(r.type){case e.QueryLexer.PRESENCE:return e.QueryParser.parsePresence;case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(i+=" with value '"+r.str+"'"),new e.QueryParseError(i,r.start,r.end)}},e.QueryParser.parsePresence=function(t){var r=t.consumeLexeme();if(void 0!=r){switch(r.str){case"-":t.currentClause.presence=e.Query.presence.PROHIBITED;break;case"+":t.currentClause.presence=e.Query.presence.REQUIRED;break;default:var i="unrecognised presence operator'"+r.str+"'";throw new e.QueryParseError(i,r.start,r.end)}var n=t.peekLexeme();if(void 0==n){var i="expecting term or field, found nothing";throw new e.QueryParseError(i,r.start,r.end)}switch(n.type){case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expecting term or field, found '"+n.type+"'";throw new e.QueryParseError(i,n.start,n.end)}}},e.QueryParser.parseField=function(t){var r=t.consumeLexeme();if(void 0!=r){if(t.query.allFields.indexOf(r.str)==-1){var i=t.query.allFields.map(function(e){return"'"+e+"'"}).join(", "),n="unrecognised field '"+r.str+"', possible fields: "+i;throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.fields=[r.str];var s=t.peekLexeme();if(void 0==s){var n="expecting term, found nothing";throw new e.QueryParseError(n,r.start,r.end)}switch(s.type){case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var n="expecting term, found '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseTerm=function(t){var r=t.consumeLexeme();if(void 0!=r){t.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(t.currentClause.usePipeline=!1);var i=t.peekLexeme();if(void 0==i)return void t.nextClause();switch(i.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+i.type+"'";throw new e.QueryParseError(n,i.start,i.end)}}},e.QueryParser.parseEditDistance=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="edit distance must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.editDistance=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseBoost=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="boost must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.boost=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.lunr=t()}(this,function(){return e})}(); diff --git a/docs/js/typeahead.jquery.js b/docs/js/typeahead.jquery.js deleted file mode 100644 index 3a2d2ab..0000000 --- a/docs/js/typeahead.jquery.js +++ /dev/null @@ -1,1694 +0,0 @@ -/*! - * typeahead.js 1.3.1 - * https://github.com/corejavascript/typeahead.js - * Copyright 2013-2020 Twitter, Inc. and other contributors; Licensed MIT - */ - - -(function(root, factory) { - if (typeof define === "function" && define.amd) { - define([ "jquery" ], function(a0) { - return factory(a0); - }); - } else if (typeof module === "object" && module.exports) { - module.exports = factory(require("jquery")); - } else { - factory(root["jQuery"]); - } -})(this, function($) { - var _ = function() { - "use strict"; - return { - isMsie: function() { - return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; - }, - isBlankString: function(str) { - return !str || /^\s*$/.test(str); - }, - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - isString: function(obj) { - return typeof obj === "string"; - }, - isNumber: function(obj) { - return typeof obj === "number"; - }, - isArray: $.isArray, - isFunction: $.isFunction, - isObject: $.isPlainObject, - isUndefined: function(obj) { - return typeof obj === "undefined"; - }, - isElement: function(obj) { - return !!(obj && obj.nodeType === 1); - }, - isJQuery: function(obj) { - return obj instanceof $; - }, - toStr: function toStr(s) { - return _.isUndefined(s) || s === null ? "" : s + ""; - }, - bind: $.proxy, - each: function(collection, cb) { - $.each(collection, reverseArgs); - function reverseArgs(index, value) { - return cb(value, index); - } - }, - map: $.map, - filter: $.grep, - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (!(result = test.call(null, val, key, obj))) { - return false; - } - }); - return !!result; - }, - some: function(obj, test) { - var result = false; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (result = test.call(null, val, key, obj)) { - return false; - } - }); - return !!result; - }, - mixin: $.extend, - identity: function(x) { - return x; - }, - clone: function(obj) { - return $.extend(true, {}, obj); - }, - getIdGenerator: function() { - var counter = 0; - return function() { - return counter++; - }; - }, - templatify: function templatify(obj) { - return $.isFunction(obj) ? obj : template; - function template() { - return String(obj); - } - }, - defer: function(fn) { - setTimeout(fn, 0); - }, - debounce: function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments, later, callNow; - later = function() { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - } - }; - callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - } - return result; - }; - }, - throttle: function(func, wait) { - var context, args, timeout, result, previous, later; - previous = 0; - later = function() { - previous = new Date(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date(), remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - stringify: function(val) { - return _.isString(val) ? val : JSON.stringify(val); - }, - guid: function() { - function _p8(s) { - var p = (Math.random().toString(16) + "000000000").substr(2, 8); - return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p; - } - return "tt-" + _p8() + _p8(true) + _p8(true) + _p8(); - }, - noop: function() {} - }; - }(); - var WWW = function() { - "use strict"; - var defaultClassNames = { - wrapper: "twitter-typeahead", - input: "tt-input", - hint: "tt-hint", - menu: "tt-menu", - dataset: "tt-dataset", - suggestion: "tt-suggestion", - selectable: "tt-selectable", - empty: "tt-empty", - open: "tt-open", - cursor: "tt-cursor", - highlight: "tt-highlight" - }; - return build; - function build(o) { - var www, classes; - classes = _.mixin({}, defaultClassNames, o); - www = { - css: buildCss(), - classes: classes, - html: buildHtml(classes), - selectors: buildSelectors(classes) - }; - return { - css: www.css, - html: www.html, - classes: www.classes, - selectors: www.selectors, - mixin: function(o) { - _.mixin(o, www); - } - }; - } - function buildHtml(c) { - return { - wrapper: '', - menu: '
' - }; - } - function buildSelectors(classes) { - var selectors = {}; - _.each(classes, function(v, k) { - selectors[k] = "." + v; - }); - return selectors; - } - function buildCss() { - var css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none", - opacity: "1" - }, - input: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - inputWithNoHint: { - position: "relative", - verticalAlign: "top" - }, - menu: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" - }, - ltr: { - left: "0", - right: "auto" - }, - rtl: { - left: "auto", - right: " 0" - } - }; - if (_.isMsie()) { - _.mixin(css.input, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); - } - return css; - } - }(); - var EventBus = function() { - "use strict"; - var namespace, deprecationMap; - namespace = "typeahead:"; - deprecationMap = { - render: "rendered", - cursorchange: "cursorchanged", - select: "selected", - autocomplete: "autocompleted" - }; - function EventBus(o) { - if (!o || !o.el) { - $.error("EventBus initialized without el"); - } - this.$el = $(o.el); - } - _.mixin(EventBus.prototype, { - _trigger: function(type, args) { - var $e = $.Event(namespace + type); - this.$el.trigger.call(this.$el, $e, args || []); - return $e; - }, - before: function(type) { - var args, $e; - args = [].slice.call(arguments, 1); - $e = this._trigger("before" + type, args); - return $e.isDefaultPrevented(); - }, - trigger: function(type) { - var deprecatedType; - this._trigger(type, [].slice.call(arguments, 1)); - if (deprecatedType = deprecationMap[type]) { - this._trigger(deprecatedType, [].slice.call(arguments, 1)); - } - } - }); - return EventBus; - }(); - var EventEmitter = function() { - "use strict"; - var splitter = /\s+/, nextTick = getNextTick(); - return { - onSync: onSync, - onAsync: onAsync, - off: off, - trigger: trigger - }; - function on(method, types, cb, context) { - var type; - if (!cb) { - return this; - } - types = types.split(splitter); - cb = context ? bindContext(cb, context) : cb; - this._callbacks = this._callbacks || {}; - while (type = types.shift()) { - this._callbacks[type] = this._callbacks[type] || { - sync: [], - async: [] - }; - this._callbacks[type][method].push(cb); - } - return this; - } - function onAsync(types, cb, context) { - return on.call(this, "async", types, cb, context); - } - function onSync(types, cb, context) { - return on.call(this, "sync", types, cb, context); - } - function off(types) { - var type; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - while (type = types.shift()) { - delete this._callbacks[type]; - } - return this; - } - function trigger(types) { - var type, callbacks, args, syncFlush, asyncFlush; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - args = [].slice.call(arguments, 1); - while ((type = types.shift()) && (callbacks = this._callbacks[type])) { - syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); - asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); - syncFlush() && nextTick(asyncFlush); - } - return this; - } - function getFlush(callbacks, context, args) { - return flush; - function flush() { - var cancelled; - for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { - cancelled = callbacks[i].apply(context, args) === false; - } - return !cancelled; - } - } - function getNextTick() { - var nextTickFn; - if (window.setImmediate) { - nextTickFn = function nextTickSetImmediate(fn) { - setImmediate(function() { - fn(); - }); - }; - } else { - nextTickFn = function nextTickSetTimeout(fn) { - setTimeout(function() { - fn(); - }, 0); - }; - } - return nextTickFn; - } - function bindContext(fn, context) { - return fn.bind ? fn.bind(context) : function() { - fn.apply(context, [].slice.call(arguments, 0)); - }; - } - }(); - var highlight = function(doc) { - "use strict"; - var defaults = { - node: null, - pattern: null, - tagName: "strong", - className: null, - wordsOnly: false, - caseSensitive: false, - diacriticInsensitive: false - }; - var accented = { - A: "[AaªÀ-Åà-åĀ-ąǍǎȀ-ȃȦȧᴬᵃḀḁẚẠ-ảₐ℀℁℻⒜Ⓐⓐ㍱-㍴㎀-㎄㎈㎉㎩-㎯㏂㏊㏟㏿Aa]", - B: "[BbᴮᵇḂ-ḇℬ⒝Ⓑⓑ㍴㎅-㎇㏃㏈㏔㏝Bb]", - C: "[CcÇçĆ-čᶜ℀ℂ℃℅℆ℭⅭⅽ⒞Ⓒⓒ㍶㎈㎉㎝㎠㎤㏄-㏇Cc]", - D: "[DdĎďDŽ-džDZ-dzᴰᵈḊ-ḓⅅⅆⅮⅾ⒟Ⓓⓓ㋏㍲㍷-㍹㎗㎭-㎯㏅㏈Dd]", - E: "[EeÈ-Ëè-ëĒ-ěȄ-ȇȨȩᴱᵉḘ-ḛẸ-ẽₑ℡ℯℰⅇ⒠Ⓔⓔ㉐㋍㋎Ee]", - F: "[FfᶠḞḟ℉ℱ℻⒡Ⓕⓕ㎊-㎌㎙ff-fflFf]", - G: "[GgĜ-ģǦǧǴǵᴳᵍḠḡℊ⒢Ⓖⓖ㋌㋍㎇㎍-㎏㎓㎬㏆㏉㏒㏿Gg]", - H: "[HhĤĥȞȟʰᴴḢ-ḫẖℋ-ℎ⒣Ⓗⓗ㋌㍱㎐-㎔㏊㏋㏗Hh]", - I: "[IiÌ-Ïì-ïĨ-İIJijǏǐȈ-ȋᴵᵢḬḭỈ-ịⁱℐℑℹⅈⅠ-ⅣⅥ-ⅨⅪⅫⅰ-ⅳⅵ-ⅸⅺⅻ⒤Ⓘⓘ㍺㏌㏕fiffiIi]", - J: "[JjIJ-ĵLJ-njǰʲᴶⅉ⒥ⒿⓙⱼJj]", - K: "[KkĶķǨǩᴷᵏḰ-ḵK⒦Ⓚⓚ㎄㎅㎉㎏㎑㎘㎞㎢㎦㎪㎸㎾㏀㏆㏍-㏏Kk]", - L: "[LlĹ-ŀLJ-ljˡᴸḶḷḺ-ḽℒℓ℡Ⅼⅼ⒧Ⓛⓛ㋏㎈㎉㏐-㏓㏕㏖㏿flfflLl]", - M: "[MmᴹᵐḾ-ṃ℠™ℳⅯⅿ⒨Ⓜⓜ㍷-㍹㎃㎆㎎㎒㎖㎙-㎨㎫㎳㎷㎹㎽㎿㏁㏂㏎㏐㏔-㏖㏘㏙㏞㏟Mm]", - N: "[NnÑñŃ-ʼnNJ-njǸǹᴺṄ-ṋⁿℕ№⒩Ⓝⓝ㎁㎋㎚㎱㎵㎻㏌㏑Nn]", - O: "[OoºÒ-Öò-öŌ-őƠơǑǒǪǫȌ-ȏȮȯᴼᵒỌ-ỏₒ℅№ℴ⒪Ⓞⓞ㍵㏇㏒㏖Oo]", - P: "[PpᴾᵖṔ-ṗℙ⒫Ⓟⓟ㉐㍱㍶㎀㎊㎩-㎬㎰㎴㎺㏋㏗-㏚Pp]", - Q: "[Qqℚ⒬Ⓠⓠ㏃Qq]", - R: "[RrŔ-řȐ-ȓʳᴿᵣṘ-ṛṞṟ₨ℛ-ℝ⒭Ⓡⓡ㋍㍴㎭-㎯㏚㏛Rr]", - S: "[SsŚ-šſȘșˢṠ-ṣ₨℁℠⒮Ⓢⓢ㎧㎨㎮-㎳㏛㏜stSs]", - T: "[TtŢ-ťȚțᵀᵗṪ-ṱẗ℡™⒯Ⓣⓣ㉐㋏㎔㏏ſtstTt]", - U: "[UuÙ-Üù-üŨ-ųƯưǓǔȔ-ȗᵁᵘᵤṲ-ṷỤ-ủ℆⒰Ⓤⓤ㍳㍺Uu]", - V: "[VvᵛᵥṼ-ṿⅣ-Ⅷⅳ-ⅷ⒱Ⓥⓥⱽ㋎㍵㎴-㎹㏜㏞Vv]", - W: "[WwŴŵʷᵂẀ-ẉẘ⒲Ⓦⓦ㎺-㎿㏝Ww]", - X: "[XxˣẊ-ẍₓ℻Ⅸ-Ⅻⅸ-ⅻ⒳Ⓧⓧ㏓Xx]", - Y: "[YyÝýÿŶ-ŸȲȳʸẎẏẙỲ-ỹ⒴Ⓨⓨ㏉Yy]", - Z: "[ZzŹ-žDZ-dzᶻẐ-ẕℤℨ⒵Ⓩⓩ㎐-㎔Zz]" - }; - return function hightlight(o) { - var regex; - o = _.mixin({}, defaults, o); - if (!o.node || !o.pattern) { - return; - } - o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; - regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly, o.diacriticInsensitive); - traverse(o.node, hightlightTextNode); - function hightlightTextNode(textNode) { - var match, patternNode, wrapperNode; - if (match = regex.exec(textNode.data)) { - wrapperNode = doc.createElement(o.tagName); - o.className && (wrapperNode.className = o.className); - patternNode = textNode.splitText(match.index); - patternNode.splitText(match[0].length); - wrapperNode.appendChild(patternNode.cloneNode(true)); - textNode.parentNode.replaceChild(wrapperNode, patternNode); - } - return !!match; - } - function traverse(el, hightlightTextNode) { - var childNode, TEXT_NODE_TYPE = 3; - for (var i = 0; i < el.childNodes.length; i++) { - childNode = el.childNodes[i]; - if (childNode.nodeType === TEXT_NODE_TYPE) { - i += hightlightTextNode(childNode) ? 1 : 0; - } else { - traverse(childNode, hightlightTextNode); - } - } - } - }; - function accent_replacer(chr) { - return accented[chr.toUpperCase()] || chr; - } - function getRegex(patterns, caseSensitive, wordsOnly, diacriticInsensitive) { - var escapedPatterns = [], regexStr; - for (var i = 0, len = patterns.length; i < len; i++) { - var escapedWord = _.escapeRegExChars(patterns[i]); - if (diacriticInsensitive) { - escapedWord = escapedWord.replace(/\S/g, accent_replacer); - } - escapedPatterns.push(escapedWord); - } - regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; - return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); - } - }(window.document); - var Input = function() { - "use strict"; - var specialKeyCodeMap; - specialKeyCodeMap = { - 9: "tab", - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - function Input(o, www) { - var id; - o = o || {}; - if (!o.input) { - $.error("input is missing"); - } - www.mixin(this); - this.$hint = $(o.hint); - this.$input = $(o.input); - this.$menu = $(o.menu); - id = this.$input.attr("id") || _.guid(); - this.$menu.attr("id", id + "_listbox"); - this.$hint.attr({ - "aria-hidden": true - }); - this.$input.attr({ - "aria-owns": id + "_listbox", - role: "combobox", - "aria-autocomplete": "list", - "aria-expanded": false - }); - this.query = this.$input.val(); - this.queryWhenFocused = this.hasFocus() ? this.query : null; - this.$overflowHelper = buildOverflowHelper(this.$input); - this._checkLanguageDirection(); - if (this.$hint.length === 0) { - this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; - } - this.onSync("cursorchange", this._updateDescendent); - } - Input.normalizeQuery = function(str) { - return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - }; - _.mixin(Input.prototype, EventEmitter, { - _onBlur: function onBlur() { - this.resetInputValue(); - this.trigger("blurred"); - }, - _onFocus: function onFocus() { - this.queryWhenFocused = this.query; - this.trigger("focused"); - }, - _onKeydown: function onKeydown($e) { - var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; - this._managePreventDefault(keyName, $e); - if (keyName && this._shouldTrigger(keyName, $e)) { - this.trigger(keyName + "Keyed", $e); - } - }, - _onInput: function onInput() { - this._setQuery(this.getInputValue()); - this.clearHintIfInvalid(); - this._checkLanguageDirection(); - }, - _managePreventDefault: function managePreventDefault(keyName, $e) { - var preventDefault; - switch (keyName) { - case "up": - case "down": - preventDefault = !withModifier($e); - break; - - default: - preventDefault = false; - } - preventDefault && $e.preventDefault(); - }, - _shouldTrigger: function shouldTrigger(keyName, $e) { - var trigger; - switch (keyName) { - case "tab": - trigger = !withModifier($e); - break; - - default: - trigger = true; - } - return trigger; - }, - _checkLanguageDirection: function checkLanguageDirection() { - var dir = (this.$input.css("direction") || "ltr").toLowerCase(); - if (this.dir !== dir) { - this.dir = dir; - this.$hint.attr("dir", dir); - this.trigger("langDirChanged", dir); - } - }, - _setQuery: function setQuery(val, silent) { - var areEquivalent, hasDifferentWhitespace; - areEquivalent = areQueriesEquivalent(val, this.query); - hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; - this.query = val; - if (!silent && !areEquivalent) { - this.trigger("queryChanged", this.query); - } else if (!silent && hasDifferentWhitespace) { - this.trigger("whitespaceChanged", this.query); - } - }, - _updateDescendent: function updateDescendent(event, id) { - this.$input.attr("aria-activedescendant", id); - }, - bind: function() { - var that = this, onBlur, onFocus, onKeydown, onInput; - onBlur = _.bind(this._onBlur, this); - onFocus = _.bind(this._onFocus, this); - onKeydown = _.bind(this._onKeydown, this); - onInput = _.bind(this._onInput, this); - this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); - if (!_.isMsie() || _.isMsie() > 9) { - this.$input.on("input.tt", onInput); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - _.defer(_.bind(that._onInput, that, $e)); - }); - } - return this; - }, - focus: function focus() { - this.$input.focus(); - }, - blur: function blur() { - this.$input.blur(); - }, - getLangDir: function getLangDir() { - return this.dir; - }, - getQuery: function getQuery() { - return this.query || ""; - }, - setQuery: function setQuery(val, silent) { - this.setInputValue(val); - this._setQuery(val, silent); - }, - hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { - return this.query !== this.queryWhenFocused; - }, - getInputValue: function getInputValue() { - return this.$input.val(); - }, - setInputValue: function setInputValue(value) { - this.$input.val(value); - this.clearHintIfInvalid(); - this._checkLanguageDirection(); - }, - resetInputValue: function resetInputValue() { - this.setInputValue(this.query); - }, - getHint: function getHint() { - return this.$hint.val(); - }, - setHint: function setHint(value) { - this.$hint.val(value); - }, - clearHint: function clearHint() { - this.setHint(""); - }, - clearHintIfInvalid: function clearHintIfInvalid() { - var val, hint, valIsPrefixOfHint, isValid; - val = this.getInputValue(); - hint = this.getHint(); - valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; - isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); - !isValid && this.clearHint(); - }, - hasFocus: function hasFocus() { - return this.$input.is(":focus"); - }, - hasOverflow: function hasOverflow() { - var constraint = this.$input.width() - 2; - this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() >= constraint; - }, - isCursorAtEnd: function() { - var valueLength, selectionStart, range; - valueLength = this.$input.val().length; - selectionStart = this.$input[0].selectionStart; - if (_.isNumber(selectionStart)) { - return selectionStart === valueLength; - } else if (document.selection) { - range = document.selection.createRange(); - range.moveStart("character", -valueLength); - return valueLength === range.text.length; - } - return true; - }, - destroy: function destroy() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$overflowHelper.remove(); - this.$hint = this.$input = this.$overflowHelper = $("
"); - }, - setAriaExpanded: function setAriaExpanded(value) { - this.$input.attr("aria-expanded", value); - } - }); - return Input; - function buildOverflowHelper($input) { - return $('').css({ - position: "absolute", - visibility: "hidden", - whiteSpace: "pre", - fontFamily: $input.css("font-family"), - fontSize: $input.css("font-size"), - fontStyle: $input.css("font-style"), - fontVariant: $input.css("font-variant"), - fontWeight: $input.css("font-weight"), - wordSpacing: $input.css("word-spacing"), - letterSpacing: $input.css("letter-spacing"), - textIndent: $input.css("text-indent"), - textRendering: $input.css("text-rendering"), - textTransform: $input.css("text-transform") - }).insertAfter($input); - } - function areQueriesEquivalent(a, b) { - return Input.normalizeQuery(a) === Input.normalizeQuery(b); - } - function withModifier($e) { - return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; - } - }(); - var Dataset = function() { - "use strict"; - var keys, nameGenerator; - keys = { - dataset: "tt-selectable-dataset", - val: "tt-selectable-display", - obj: "tt-selectable-object" - }; - nameGenerator = _.getIdGenerator(); - function Dataset(o, www) { - o = o || {}; - o.templates = o.templates || {}; - o.templates.notFound = o.templates.notFound || o.templates.empty; - if (!o.source) { - $.error("missing source"); - } - if (!o.node) { - $.error("missing node"); - } - if (o.name && !isValidName(o.name)) { - $.error("invalid dataset name: " + o.name); - } - www.mixin(this); - this.highlight = !!o.highlight; - this.name = _.toStr(o.name || nameGenerator()); - this.limit = o.limit || 5; - this.displayFn = getDisplayFn(o.display || o.displayKey); - this.templates = getTemplates(o.templates, this.displayFn); - this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; - this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; - this._resetLastSuggestion(); - this.$el = $(o.node).attr("role", "presentation").addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); - } - Dataset.extractData = function extractData(el) { - var $el = $(el); - if ($el.data(keys.obj)) { - return { - dataset: $el.data(keys.dataset) || "", - val: $el.data(keys.val) || "", - obj: $el.data(keys.obj) || null - }; - } - return null; - }; - _.mixin(Dataset.prototype, EventEmitter, { - _overwrite: function overwrite(query, suggestions) { - suggestions = suggestions || []; - if (suggestions.length) { - this._renderSuggestions(query, suggestions); - } else if (this.async && this.templates.pending) { - this._renderPending(query); - } else if (!this.async && this.templates.notFound) { - this._renderNotFound(query); - } else { - this._empty(); - } - this.trigger("rendered", suggestions, false, this.name); - }, - _append: function append(query, suggestions) { - suggestions = suggestions || []; - if (suggestions.length && this.$lastSuggestion.length) { - this._appendSuggestions(query, suggestions); - } else if (suggestions.length) { - this._renderSuggestions(query, suggestions); - } else if (!this.$lastSuggestion.length && this.templates.notFound) { - this._renderNotFound(query); - } - this.trigger("rendered", suggestions, true, this.name); - }, - _renderSuggestions: function renderSuggestions(query, suggestions) { - var $fragment; - $fragment = this._getSuggestionsFragment(query, suggestions); - this.$lastSuggestion = $fragment.children().last(); - this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); - }, - _appendSuggestions: function appendSuggestions(query, suggestions) { - var $fragment, $lastSuggestion; - $fragment = this._getSuggestionsFragment(query, suggestions); - $lastSuggestion = $fragment.children().last(); - this.$lastSuggestion.after($fragment); - this.$lastSuggestion = $lastSuggestion; - }, - _renderPending: function renderPending(query) { - var template = this.templates.pending; - this._resetLastSuggestion(); - template && this.$el.html(template({ - query: query, - dataset: this.name - })); - }, - _renderNotFound: function renderNotFound(query) { - var template = this.templates.notFound; - this._resetLastSuggestion(); - template && this.$el.html(template({ - query: query, - dataset: this.name - })); - }, - _empty: function empty() { - this.$el.empty(); - this._resetLastSuggestion(); - }, - _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { - var that = this, fragment; - fragment = document.createDocumentFragment(); - _.each(suggestions, function getSuggestionNode(suggestion) { - var $el, context; - context = that._injectQuery(query, suggestion); - $el = $(that.templates.suggestion(context)).data(keys.dataset, that.name).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); - fragment.appendChild($el[0]); - }); - this.highlight && highlight({ - className: this.classes.highlight, - node: fragment, - pattern: query - }); - return $(fragment); - }, - _getFooter: function getFooter(query, suggestions) { - return this.templates.footer ? this.templates.footer({ - query: query, - suggestions: suggestions, - dataset: this.name - }) : null; - }, - _getHeader: function getHeader(query, suggestions) { - return this.templates.header ? this.templates.header({ - query: query, - suggestions: suggestions, - dataset: this.name - }) : null; - }, - _resetLastSuggestion: function resetLastSuggestion() { - this.$lastSuggestion = $(); - }, - _injectQuery: function injectQuery(query, obj) { - return _.isObject(obj) ? _.mixin({ - _query: query - }, obj) : obj; - }, - update: function update(query) { - var that = this, canceled = false, syncCalled = false, rendered = 0; - this.cancel(); - this.cancel = function cancel() { - canceled = true; - that.cancel = $.noop; - that.async && that.trigger("asyncCanceled", query, that.name); - }; - this.source(query, sync, async); - !syncCalled && sync([]); - function sync(suggestions) { - if (syncCalled) { - return; - } - syncCalled = true; - suggestions = (suggestions || []).slice(0, that.limit); - rendered = suggestions.length; - that._overwrite(query, suggestions); - if (rendered < that.limit && that.async) { - that.trigger("asyncRequested", query, that.name); - } - } - function async(suggestions) { - suggestions = suggestions || []; - if (!canceled && rendered < that.limit) { - that.cancel = $.noop; - var idx = Math.abs(rendered - that.limit); - rendered += idx; - that._append(query, suggestions.slice(0, idx)); - that.async && that.trigger("asyncReceived", query, that.name); - } - } - }, - cancel: $.noop, - clear: function clear() { - this._empty(); - this.cancel(); - this.trigger("cleared"); - }, - isEmpty: function isEmpty() { - return this.$el.is(":empty"); - }, - destroy: function destroy() { - this.$el = $("
"); - } - }); - return Dataset; - function getDisplayFn(display) { - display = display || _.stringify; - return _.isFunction(display) ? display : displayFn; - function displayFn(obj) { - return obj[display]; - } - } - function getTemplates(templates, displayFn) { - return { - notFound: templates.notFound && _.templatify(templates.notFound), - pending: templates.pending && _.templatify(templates.pending), - header: templates.header && _.templatify(templates.header), - footer: templates.footer && _.templatify(templates.footer), - suggestion: templates.suggestion ? userSuggestionTemplate : suggestionTemplate - }; - function userSuggestionTemplate(context) { - var template = templates.suggestion; - return $(template(context)).attr("id", _.guid()); - } - function suggestionTemplate(context) { - return $('
').attr("id", _.guid()).text(displayFn(context)); - } - } - function isValidName(str) { - return /^[_a-zA-Z0-9-]+$/.test(str); - } - }(); - var Menu = function() { - "use strict"; - function Menu(o, www) { - var that = this; - o = o || {}; - if (!o.node) { - $.error("node is required"); - } - www.mixin(this); - this.$node = $(o.node); - this.query = null; - this.datasets = _.map(o.datasets, initializeDataset); - function initializeDataset(oDataset) { - var node = that.$node.find(oDataset.node).first(); - oDataset.node = node.length ? node : $("
").appendTo(that.$node); - return new Dataset(oDataset, www); - } - } - _.mixin(Menu.prototype, EventEmitter, { - _onSelectableClick: function onSelectableClick($e) { - this.trigger("selectableClicked", $($e.currentTarget)); - }, - _onRendered: function onRendered(type, dataset, suggestions, async) { - this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); - this.trigger("datasetRendered", dataset, suggestions, async); - }, - _onCleared: function onCleared() { - this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); - this.trigger("datasetCleared"); - }, - _propagate: function propagate() { - this.trigger.apply(this, arguments); - }, - _allDatasetsEmpty: function allDatasetsEmpty() { - return _.every(this.datasets, _.bind(function isDatasetEmpty(dataset) { - var isEmpty = dataset.isEmpty(); - this.$node.attr("aria-expanded", !isEmpty); - return isEmpty; - }, this)); - }, - _getSelectables: function getSelectables() { - return this.$node.find(this.selectors.selectable); - }, - _removeCursor: function _removeCursor() { - var $selectable = this.getActiveSelectable(); - $selectable && $selectable.removeClass(this.classes.cursor); - }, - _ensureVisible: function ensureVisible($el) { - var elTop, elBottom, nodeScrollTop, nodeHeight; - elTop = $el.position().top; - elBottom = elTop + $el.outerHeight(true); - nodeScrollTop = this.$node.scrollTop(); - nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); - if (elTop < 0) { - this.$node.scrollTop(nodeScrollTop + elTop); - } else if (nodeHeight < elBottom) { - this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); - } - }, - bind: function() { - var that = this, onSelectableClick; - onSelectableClick = _.bind(this._onSelectableClick, this); - this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); - this.$node.on("mouseover", this.selectors.selectable, function() { - that.setCursor($(this)); - }); - this.$node.on("mouseleave", function() { - that._removeCursor(); - }); - _.each(this.datasets, function(dataset) { - dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); - }); - return this; - }, - isOpen: function isOpen() { - return this.$node.hasClass(this.classes.open); - }, - open: function open() { - this.$node.scrollTop(0); - this.$node.addClass(this.classes.open); - }, - close: function close() { - this.$node.attr("aria-expanded", false); - this.$node.removeClass(this.classes.open); - this._removeCursor(); - }, - setLanguageDirection: function setLanguageDirection(dir) { - this.$node.attr("dir", dir); - }, - selectableRelativeToCursor: function selectableRelativeToCursor(delta) { - var $selectables, $oldCursor, oldIndex, newIndex; - $oldCursor = this.getActiveSelectable(); - $selectables = this._getSelectables(); - oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; - newIndex = oldIndex + delta; - newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; - newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; - return newIndex === -1 ? null : $selectables.eq(newIndex); - }, - setCursor: function setCursor($selectable) { - this._removeCursor(); - if ($selectable = $selectable && $selectable.first()) { - $selectable.addClass(this.classes.cursor); - this._ensureVisible($selectable); - } - }, - getSelectableData: function getSelectableData($el) { - return $el && $el.length ? Dataset.extractData($el) : null; - }, - getActiveSelectable: function getActiveSelectable() { - var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); - return $selectable.length ? $selectable : null; - }, - getTopSelectable: function getTopSelectable() { - var $selectable = this._getSelectables().first(); - return $selectable.length ? $selectable : null; - }, - update: function update(query) { - var isValidUpdate = query !== this.query; - if (isValidUpdate) { - this.query = query; - _.each(this.datasets, updateDataset); - } - return isValidUpdate; - function updateDataset(dataset) { - dataset.update(query); - } - }, - empty: function empty() { - _.each(this.datasets, clearDataset); - this.query = null; - this.$node.addClass(this.classes.empty); - function clearDataset(dataset) { - dataset.clear(); - } - }, - destroy: function destroy() { - this.$node.off(".tt"); - this.$node = $("
"); - _.each(this.datasets, destroyDataset); - function destroyDataset(dataset) { - dataset.destroy(); - } - } - }); - return Menu; - }(); - var Status = function() { - "use strict"; - function Status(options) { - this.$el = $("", { - role: "status", - "aria-live": "polite" - }).css({ - position: "absolute", - padding: "0", - border: "0", - height: "1px", - width: "1px", - "margin-bottom": "-1px", - "margin-right": "-1px", - overflow: "hidden", - clip: "rect(0 0 0 0)", - "white-space": "nowrap" - }); - options.$input.after(this.$el); - _.each(options.menu.datasets, _.bind(function(dataset) { - if (dataset.onSync) { - dataset.onSync("rendered", _.bind(this.update, this)); - dataset.onSync("cleared", _.bind(this.cleared, this)); - } - }, this)); - } - _.mixin(Status.prototype, { - update: function update(event, suggestions) { - var length = suggestions.length; - var words; - if (length === 1) { - words = { - result: "result", - is: "is" - }; - } else { - words = { - result: "results", - is: "are" - }; - } - this.$el.text(length + " " + words.result + " " + words.is + " available, use up and down arrow keys to navigate."); - }, - cleared: function() { - this.$el.text(""); - } - }); - return Status; - }(); - var DefaultMenu = function() { - "use strict"; - var s = Menu.prototype; - function DefaultMenu() { - Menu.apply(this, [].slice.call(arguments, 0)); - } - _.mixin(DefaultMenu.prototype, Menu.prototype, { - open: function open() { - !this._allDatasetsEmpty() && this._show(); - return s.open.apply(this, [].slice.call(arguments, 0)); - }, - close: function close() { - this._hide(); - return s.close.apply(this, [].slice.call(arguments, 0)); - }, - _onRendered: function onRendered() { - if (this._allDatasetsEmpty()) { - this._hide(); - } else { - this.isOpen() && this._show(); - } - return s._onRendered.apply(this, [].slice.call(arguments, 0)); - }, - _onCleared: function onCleared() { - if (this._allDatasetsEmpty()) { - this._hide(); - } else { - this.isOpen() && this._show(); - } - return s._onCleared.apply(this, [].slice.call(arguments, 0)); - }, - setLanguageDirection: function setLanguageDirection(dir) { - this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); - return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); - }, - _hide: function hide() { - this.$node.hide(); - }, - _show: function show() { - this.$node.css("display", "block"); - } - }); - return DefaultMenu; - }(); - var Typeahead = function() { - "use strict"; - function Typeahead(o, www) { - var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; - o = o || {}; - if (!o.input) { - $.error("missing input"); - } - if (!o.menu) { - $.error("missing menu"); - } - if (!o.eventBus) { - $.error("missing event bus"); - } - www.mixin(this); - this.eventBus = o.eventBus; - this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; - this.input = o.input; - this.menu = o.menu; - this.enabled = true; - this.autoselect = !!o.autoselect; - this.active = false; - this.input.hasFocus() && this.activate(); - this.dir = this.input.getLangDir(); - this._hacks(); - this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); - onFocused = c(this, "activate", "open", "_onFocused"); - onBlurred = c(this, "deactivate", "_onBlurred"); - onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); - onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); - onEscKeyed = c(this, "isActive", "_onEscKeyed"); - onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); - onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); - onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); - onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); - onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); - onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); - this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); - } - _.mixin(Typeahead.prototype, { - _hacks: function hacks() { - var $input, $menu; - $input = this.input.$input || $("
"); - $menu = this.menu.$node || $("
"); - $input.on("blur.tt", function($e) { - var active, isActive, hasActive; - active = document.activeElement; - isActive = $menu.is(active); - hasActive = $menu.has(active).length > 0; - if (_.isMsie() && (isActive || hasActive)) { - $e.preventDefault(); - $e.stopImmediatePropagation(); - _.defer(function() { - $input.focus(); - }); - } - }); - $menu.on("mousedown.tt", function($e) { - $e.preventDefault(); - }); - }, - _onSelectableClicked: function onSelectableClicked(type, $el) { - this.select($el); - }, - _onDatasetCleared: function onDatasetCleared() { - this._updateHint(); - }, - _onDatasetRendered: function onDatasetRendered(type, suggestions, async, dataset) { - this._updateHint(); - if (this.autoselect) { - var cursorClass = this.selectors.cursor.substr(1); - this.menu.$node.find(this.selectors.suggestion).first().addClass(cursorClass); - } - this.eventBus.trigger("render", suggestions, async, dataset); - }, - _onAsyncRequested: function onAsyncRequested(type, dataset, query) { - this.eventBus.trigger("asyncrequest", query, dataset); - }, - _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { - this.eventBus.trigger("asynccancel", query, dataset); - }, - _onAsyncReceived: function onAsyncReceived(type, dataset, query) { - this.eventBus.trigger("asyncreceive", query, dataset); - }, - _onFocused: function onFocused() { - this._minLengthMet() && this.menu.update(this.input.getQuery()); - }, - _onBlurred: function onBlurred() { - if (this.input.hasQueryChangedSinceLastFocus()) { - this.eventBus.trigger("change", this.input.getQuery()); - } - }, - _onEnterKeyed: function onEnterKeyed(type, $e) { - var $selectable; - if ($selectable = this.menu.getActiveSelectable()) { - if (this.select($selectable)) { - $e.preventDefault(); - $e.stopPropagation(); - } - } else if (this.autoselect) { - if (this.select(this.menu.getTopSelectable())) { - $e.preventDefault(); - $e.stopPropagation(); - } - } - }, - _onTabKeyed: function onTabKeyed(type, $e) { - var $selectable; - if ($selectable = this.menu.getActiveSelectable()) { - this.select($selectable) && $e.preventDefault(); - } else if (this.autoselect) { - if ($selectable = this.menu.getTopSelectable()) { - this.autocomplete($selectable) && $e.preventDefault(); - } - } - }, - _onEscKeyed: function onEscKeyed() { - this.close(); - }, - _onUpKeyed: function onUpKeyed() { - this.moveCursor(-1); - }, - _onDownKeyed: function onDownKeyed() { - this.moveCursor(+1); - }, - _onLeftKeyed: function onLeftKeyed() { - if (this.dir === "rtl" && this.input.isCursorAtEnd()) { - this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable()); - } - }, - _onRightKeyed: function onRightKeyed() { - if (this.dir === "ltr" && this.input.isCursorAtEnd()) { - this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable()); - } - }, - _onQueryChanged: function onQueryChanged(e, query) { - this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); - }, - _onWhitespaceChanged: function onWhitespaceChanged() { - this._updateHint(); - }, - _onLangDirChanged: function onLangDirChanged(e, dir) { - if (this.dir !== dir) { - this.dir = dir; - this.menu.setLanguageDirection(dir); - } - }, - _openIfActive: function openIfActive() { - this.isActive() && this.open(); - }, - _minLengthMet: function minLengthMet(query) { - query = _.isString(query) ? query : this.input.getQuery() || ""; - return query.length >= this.minLength; - }, - _updateHint: function updateHint() { - var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; - $selectable = this.menu.getTopSelectable(); - data = this.menu.getSelectableData($selectable); - val = this.input.getInputValue(); - if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { - query = Input.normalizeQuery(val); - escapedQuery = _.escapeRegExChars(query); - frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); - match = frontMatchRegEx.exec(data.val); - match && this.input.setHint(val + match[1]); - } else { - this.input.clearHint(); - } - }, - isEnabled: function isEnabled() { - return this.enabled; - }, - enable: function enable() { - this.enabled = true; - }, - disable: function disable() { - this.enabled = false; - }, - isActive: function isActive() { - return this.active; - }, - activate: function activate() { - if (this.isActive()) { - return true; - } else if (!this.isEnabled() || this.eventBus.before("active")) { - return false; - } else { - this.active = true; - this.eventBus.trigger("active"); - return true; - } - }, - deactivate: function deactivate() { - if (!this.isActive()) { - return true; - } else if (this.eventBus.before("idle")) { - return false; - } else { - this.active = false; - this.close(); - this.eventBus.trigger("idle"); - return true; - } - }, - isOpen: function isOpen() { - return this.menu.isOpen(); - }, - open: function open() { - if (!this.isOpen() && !this.eventBus.before("open")) { - this.input.setAriaExpanded(true); - this.menu.open(); - this._updateHint(); - this.eventBus.trigger("open"); - } - return this.isOpen(); - }, - close: function close() { - if (this.isOpen() && !this.eventBus.before("close")) { - this.input.setAriaExpanded(false); - this.menu.close(); - this.input.clearHint(); - this.input.resetInputValue(); - this.eventBus.trigger("close"); - } - return !this.isOpen(); - }, - setVal: function setVal(val) { - this.input.setQuery(_.toStr(val)); - }, - getVal: function getVal() { - return this.input.getQuery(); - }, - select: function select($selectable) { - var data = this.menu.getSelectableData($selectable); - if (data && !this.eventBus.before("select", data.obj, data.dataset)) { - this.input.setQuery(data.val, true); - this.eventBus.trigger("select", data.obj, data.dataset); - this.close(); - return true; - } - return false; - }, - autocomplete: function autocomplete($selectable) { - var query, data, isValid; - query = this.input.getQuery(); - data = this.menu.getSelectableData($selectable); - isValid = data && query !== data.val; - if (isValid && !this.eventBus.before("autocomplete", data.obj, data.dataset)) { - this.input.setQuery(data.val); - this.eventBus.trigger("autocomplete", data.obj, data.dataset); - return true; - } - return false; - }, - moveCursor: function moveCursor(delta) { - var query, $candidate, data, suggestion, datasetName, cancelMove, id; - query = this.input.getQuery(); - $candidate = this.menu.selectableRelativeToCursor(delta); - data = this.menu.getSelectableData($candidate); - suggestion = data ? data.obj : null; - datasetName = data ? data.dataset : null; - id = $candidate ? $candidate.attr("id") : null; - this.input.trigger("cursorchange", id); - cancelMove = this._minLengthMet() && this.menu.update(query); - if (!cancelMove && !this.eventBus.before("cursorchange", suggestion, datasetName)) { - this.menu.setCursor($candidate); - if (data) { - if (typeof data.val === "string") { - this.input.setInputValue(data.val); - } - } else { - this.input.resetInputValue(); - this._updateHint(); - } - this.eventBus.trigger("cursorchange", suggestion, datasetName); - return true; - } - return false; - }, - destroy: function destroy() { - this.input.destroy(); - this.menu.destroy(); - } - }); - return Typeahead; - function c(ctx) { - var methods = [].slice.call(arguments, 1); - return function() { - var args = [].slice.call(arguments); - _.each(methods, function(method) { - return ctx[method].apply(ctx, args); - }); - }; - } - }(); - (function() { - "use strict"; - var old, keys, methods; - old = $.fn.typeahead; - keys = { - www: "tt-www", - attrs: "tt-attrs", - typeahead: "tt-typeahead" - }; - methods = { - initialize: function initialize(o, datasets) { - var www; - datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); - o = o || {}; - www = WWW(o.classNames); - return this.each(attach); - function attach() { - var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, status, typeahead, MenuConstructor; - _.each(datasets, function(d) { - d.highlight = !!o.highlight; - }); - $input = $(this); - $wrapper = $(www.html.wrapper); - $hint = $elOrNull(o.hint); - $menu = $elOrNull(o.menu); - defaultHint = o.hint !== false && !$hint; - defaultMenu = o.menu !== false && !$menu; - defaultHint && ($hint = buildHintFromInput($input, www)); - defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); - $hint && $hint.val(""); - $input = prepInput($input, www); - if (defaultHint || defaultMenu) { - $wrapper.css(www.css.wrapper); - $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); - $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); - } - MenuConstructor = defaultMenu ? DefaultMenu : Menu; - eventBus = new EventBus({ - el: $input - }); - input = new Input({ - hint: $hint, - input: $input, - menu: $menu - }, www); - menu = new MenuConstructor({ - node: $menu, - datasets: datasets - }, www); - status = new Status({ - $input: $input, - menu: menu - }); - typeahead = new Typeahead({ - input: input, - menu: menu, - eventBus: eventBus, - minLength: o.minLength, - autoselect: o.autoselect - }, www); - $input.data(keys.www, www); - $input.data(keys.typeahead, typeahead); - } - }, - isEnabled: function isEnabled() { - var enabled; - ttEach(this.first(), function(t) { - enabled = t.isEnabled(); - }); - return enabled; - }, - enable: function enable() { - ttEach(this, function(t) { - t.enable(); - }); - return this; - }, - disable: function disable() { - ttEach(this, function(t) { - t.disable(); - }); - return this; - }, - isActive: function isActive() { - var active; - ttEach(this.first(), function(t) { - active = t.isActive(); - }); - return active; - }, - activate: function activate() { - ttEach(this, function(t) { - t.activate(); - }); - return this; - }, - deactivate: function deactivate() { - ttEach(this, function(t) { - t.deactivate(); - }); - return this; - }, - isOpen: function isOpen() { - var open; - ttEach(this.first(), function(t) { - open = t.isOpen(); - }); - return open; - }, - open: function open() { - ttEach(this, function(t) { - t.open(); - }); - return this; - }, - close: function close() { - ttEach(this, function(t) { - t.close(); - }); - return this; - }, - select: function select(el) { - var success = false, $el = $(el); - ttEach(this.first(), function(t) { - success = t.select($el); - }); - return success; - }, - autocomplete: function autocomplete(el) { - var success = false, $el = $(el); - ttEach(this.first(), function(t) { - success = t.autocomplete($el); - }); - return success; - }, - moveCursor: function moveCursoe(delta) { - var success = false; - ttEach(this.first(), function(t) { - success = t.moveCursor(delta); - }); - return success; - }, - val: function val(newVal) { - var query; - if (!arguments.length) { - ttEach(this.first(), function(t) { - query = t.getVal(); - }); - return query; - } else { - ttEach(this, function(t) { - t.setVal(_.toStr(newVal)); - }); - return this; - } - }, - destroy: function destroy() { - ttEach(this, function(typeahead, $input) { - revert($input); - typeahead.destroy(); - }); - return this; - } - }; - $.fn.typeahead = function(method) { - if (methods[method]) { - return methods[method].apply(this, [].slice.call(arguments, 1)); - } else { - return methods.initialize.apply(this, arguments); - } - }; - $.fn.typeahead.noConflict = function noConflict() { - $.fn.typeahead = old; - return this; - }; - function ttEach($els, fn) { - $els.each(function() { - var $input = $(this), typeahead; - (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); - }); - } - function buildHintFromInput($input, www) { - return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop({ - readonly: true, - required: false - }).removeAttr("id name placeholder").removeClass("required").attr({ - spellcheck: "false", - tabindex: -1 - }); - } - function prepInput($input, www) { - $input.data(keys.attrs, { - dir: $input.attr("dir"), - autocomplete: $input.attr("autocomplete"), - spellcheck: $input.attr("spellcheck"), - style: $input.attr("style") - }); - $input.addClass(www.classes.input).attr({ - spellcheck: false - }); - try { - !$input.attr("dir") && $input.attr("dir", "auto"); - } catch (e) {} - return $input; - } - function getBackgroundStyles($el) { - return { - backgroundAttachment: $el.css("background-attachment"), - backgroundClip: $el.css("background-clip"), - backgroundColor: $el.css("background-color"), - backgroundImage: $el.css("background-image"), - backgroundOrigin: $el.css("background-origin"), - backgroundPosition: $el.css("background-position"), - backgroundRepeat: $el.css("background-repeat"), - backgroundSize: $el.css("background-size") - }; - } - function revert($input) { - var www, $wrapper; - www = $input.data(keys.www); - $wrapper = $input.parent().filter(www.selectors.wrapper); - _.each($input.data(keys.attrs), function(val, key) { - _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); - }); - $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); - if ($wrapper.length) { - $input.detach().insertAfter($wrapper); - $wrapper.remove(); - } - } - function $elOrNull(obj) { - var isValid, $el; - isValid = _.isJQuery(obj) || _.isElement(obj); - $el = isValid ? $(obj).first() : []; - return $el.length ? $el : null; - } - })(); -}); \ No newline at end of file diff --git a/docs/protobufsupport.html b/docs/protobufsupport.html deleted file mode 100644 index 7c03174..0000000 --- a/docs/protobufsupport.html +++ /dev/null @@ -1,514 +0,0 @@ - - - - ProtobufSupport Reference - - - - - - - - - - - - -
-
-

Docs (100% documented)

-

GitHubView on GitHub

-
-
- -
-
-
-
-
- -
-
- -
-
-
- -

Protocol Buffer Compatibility

- -

BinaryCodable provides limited compatibility to Google Protocol Buffers. Certain Swift types can be encoded to protobuf compatible binary data, and vice versa. The standard binary format is similar to protobuf, but includes some deviations to support all Swift types and features. There are additional ProtobufEncoder and ProtobufDecoder types which change the encoding format to be protobuf-compatible, at the expense of errors for unsupported features.

- -

For a description of the Protocol Buffer format, see the official documentation.

- -

Important notes

- -
    -
  • Advanced protobuf features like message concatenation are not supported.
  • -
  • Unsupported features of Protobuf may cause the encoding to fail with a ProtobufEncodingError. Interoperability should be thoroughly checked through testing.
  • -
-

Usage

- -

The conversion process is equivalent to the BinaryEncoder and BinaryDecoder types.

-
import BinaryCodable
-
-

Encoding

- -

Construct an encoder when converting instances to binary data, and feed the message(s) into it:

-
let message: Message = ...
-
-let encoder = ProtobufEncoder()
-let data = try encoder.encode(message)
-
-

Decoding

- -

Decoding instances from binary data works much the same way:

-
let decoder = ProtobufDecoder()
-let message = try decoder.decode(Message.self, from: data)
-
- -

Alternatively, the type can be inferred:

-
let message: Message = try decoder.decode(from: data)
-
-

Errors

- -

It is possible for both encoding and decoding to fail. -All possible errors occuring during encoding produce BinaryEncodingError or ProtobufEncodingError errors, while unsuccessful decoding produces BinaryDecodingError or ProtobufDecodingError. -All are enums with several cases describing the nature of the error. -See the documentation of the types to learn more about the different error conditions.

-

Message definition

- -

Protobuf organizes data into messages, which are structures with keyed fields. Compatible Swift types must be similar, so either struct or class. Simple types like Int or Bool are not supported on the root level, and neither are Dictionary, Array or enum. It’s best to think of the proto definitions and construct Swift types in the same way. Let’s look at an example from the Protocol Buffer documentation:

-
message SearchRequest {
-    string query = 1;
-    int32 page_number = 2;
-    int32 result_per_page = 3;
-}
-
- -

The corresponding Swift definition would be:

-
struct SearchRequest: Codable {
-
-    var query: String
-
-    var pageNumber: Int32
-
-    var resultPerPage: Int32
-
-    enum CodingKeys: Int, CodingKey {
-        case query = 1
-        case pageNumber = 2
-        case resultPerPage = 3
-    }
-}
-
- -

The general structure of the messages is very similar, with the proto field numbers specified as integer coding keys.

-

Assigning integer keys

- -

The assignment of integer keys follow the same rules as for field numbers, just written out as an enum with RawValue == Int on the type conforming to CodingKey. The smallest field number you can specify is 1, and the largest is 2^29 - 1, or 536,870,911. Codable types without (or with invalid) integer keys can’t be encoded using ProtobufEncoder and will throw an error.

-

Scalar value types

- -

There are several scalar types defined for Protocol Buffers, which are the basic building blocks of messages. BinaryCodable provides Swift equivalents for each of them:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Protobuf primitiveSwift equivalentComment
doubleDoubleAlways 8 byte
floatFloatAlways 4 byte
int32Int32Uses variable-length encoding
int64Int64Uses variable-length encoding
uint32UInt32Uses variable-length encoding
uint64UInt64Uses variable-length encoding
sint32SignedInteger<Int32>Uses ZigZag encoding, see SignedInteger wrapper
sint64SignedInteger<Int64>Uses ZigZag encoding, see SignedInteger wrapper
fixed32FixedSize<UInt32>See FixedSize wrapper
fixed64FixedSize<UInt64>See FixedSize wrapper
sfixed32FixedSize<Int32>See FixedSize wrapper
sfixed64FixedSize<Int64>See FixedSize wrapper
boolBoolAlways 1 byte
stringStringEncoded using UTF-8
bytesDataEncoded as-is
messagestructNested messages are also supported.
repeatedArrayScalar values must always be packed (the proto3 default)
enumEnumSee Enums
oneofEnumSee OneOf Definition
- -

The Swift types Int8, UInt8, Int16, and UInt16 are not supported, and will result in an error.

- -

Note: Int and UInt values are always encoded as 64-bit numbers, despite the fact that they might be 32-bit values on some systems. Decoding a 64-bit value on a 32-bit system will result in an error.

-

Property wrappers

- -

The Protocol Buffer format provides several different encoding strategies for integers to minimize the binary size depending on the encoded values. By default, all integers are encoded using Base 128 Varints, but this can be changed using Swift PropertyWrappers. The following encoding options exist:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Swift typeVarint encodingZigZag EncodingFixed-size encoding
Int32Int32SignedInteger<Int32>FixedSize<Int32>
Int64Int64SignedInteger<Int64>FixedSize<Int64>
UInt32UInt32-FixedSize<UInt32>
UInt64UInt64-FixedSize<UInt64>
-

Fixed size integers

- -

While varints are efficient for small numbers, their encoding introduces a storage and computation penalty when the integers are often large, e.g. for random numbers. BinaryCodable provides the FixedSize wrapper, which forces integers to be encoded using their little-endian binary representations. This means that e.g. an Int32 is always encoded as 4 byte (instead of 1-5 bytes using Varint encoding). This makes 32-bit FixedSize types more efficient than Varint if values are often larger than 2^28 (2^56 for 64-bit types).

- -

Use the property wrapper within a Codable definition to enforce fixed-width encoding for a property:

-
struct MyStruct: Codable {
-
-    /// Always encoded as 4 bytes
-    @FixedSize 
-    var largeInteger: Int32
-}
-
- -

The FixedSize wrapper is available to all Varint types: Int, UInt, Int32, UInt32, Int64, and UInt64.

-

Signed integers

- -

Integers are by default encoded as Varint values, which is efficient while numbers are small and positive. For numbers which are mostly or also often negative, it is more efficient to store them using Zig-Zag encoding. BinaryCodable offers the SignedValue wrapper that can be applied to Int, Int32 and Int64 properties to increase the efficiency for negative values.

- -

Whenever your integers are expected to be negative, then you should apply the wrapper:

-
struct MyStruct: Codable {
-
-    /// More efficiently encodes negative numbers
-    @SignedValue 
-    var count: Int
-}
-
-

Enums

- -

Protocol Buffer enumerations are supported, with a few notable caveats. Here is the example from the official documentation:

-
message SearchRequest {
-
-    ...
-
-    enum Corpus {
-        UNIVERSAL = 0;
-        WEB = 1;
-        IMAGES = 2;
-        LOCAL = 3;
-        NEWS = 4;
-        PRODUCTS = 5;
-        VIDEO = 6;
-    }
-    Corpus corpus = 4;
-}
-
- -

The BinaryCodable Swift equivalent would be:

-
struct SearchRequest: Codable {
-
-    ...
-
-    enum Corpus: Int, Codable {
-        case universal = 0
-        case web = 1
-        case images = 2
-        case local = 3
-        case news = 4
-        case products = 5
-        case video = 6
-    }
-
-    var corpus: Corpus
-
-    enum CodingKeys: Int, CodingKey {
-        case corpus = 4
-    }
-}
-
- -

It should be noted that protobuf enums require a default key 0.

-

Oneof

- -

The protobuf feature Oneof can also be supported using a special enum definition. Given the protobuf definition (from here):

-
syntax = "proto3";
-message ExampleOneOf {
-   int32 field1 = 1;
-   oneof alternatives {
-       int64 id = 2;
-       string name = 3;
-   }
-}
-
- -

The corresponding Swift definition would be:

-
struct ExampleOneOf: Codable {
-
-    let field1: Int32
-
-    // The oneof field
-    let alternatives: Alternatives
-
-    // The OneOf definition
-    enum Alternatives: Codable, ProtobufOneOf {
-        case id(Int64)
-        case name(String)
-
-        // Field values, must not overlap with `ExampleOneOf.CodingKeys`
-        enum CodingKeys: Int, CodingKey {
-            case id = 2
-            case name = 3
-        }
-    }
-
-    enum CodingKeys: Int, CodingKey {
-        case field1 = 1
-        // The field id of the Oneof field is not used
-        case alternatives = 123456
-    }
- }
-
- -

Note that the Alternatives enum must conform to ProtobufOneOf, which changes the encoding to create compatibility with the Protobuf binary format.

- -

Important The ProtobufOneOf protocol must not be applied to any other types, or encoding/decoding will fail.

- -
-
- -
-
- - diff --git a/docs/search.json b/docs/search.json deleted file mode 100644 index 913b7f7..0000000 --- a/docs/search.json +++ /dev/null @@ -1 +0,0 @@ -{"Structs/SignedValue.html#/s:s27ExpressibleByIntegerLiteralP0cD4TypeQa":{"name":"IntegerLiteralType","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV07wrappedD0xvp":{"name":"wrappedValue","abstract":"

The value wrapped in the fixed-size container

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV07wrappedD0ACyxGx_tcfc":{"name":"init(wrappedValue:)","abstract":"

Wrap an integer value in a fixed-size container

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:s27ExpressibleByIntegerLiteralP07integerD0x0cD4TypeQz_tcfc":{"name":"init(integerLiteral:)","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:SL1loiySbx_xtFZ":{"name":"<(_:_:)","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV6encode2toys7Encoder_p_tKF":{"name":"encode(to:)","abstract":"

Encode the wrapped value transparently to the given encoder.

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV4fromACyxGs7Decoder_p_tKcfc":{"name":"init(from:)","abstract":"

Decode a wrapped value from a decoder.

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV4zeroACyxGvpZ":{"name":"zero","abstract":"

The zero value.

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV3maxACyxGvpZ":{"name":"max","abstract":"

The maximum representable integer in this type.

","parent_name":"SignedValue"},"Structs/SignedValue.html#/s:13BinaryCodable11SignedValueV3minACyxGvpZ":{"name":"min","abstract":"

The minimum representable integer in this type.

","parent_name":"SignedValue"},"Structs/FixedSize.html#/s:s27ExpressibleByIntegerLiteralP0cD4TypeQa":{"name":"IntegerLiteralType","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV12wrappedValuexvp":{"name":"wrappedValue","abstract":"

The value wrapped in the fixed-size container

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV12wrappedValueACyxGx_tcfc":{"name":"init(wrappedValue:)","abstract":"

Wrap an integer value in a fixed-size container

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:s27ExpressibleByIntegerLiteralP07integerD0x0cD4TypeQz_tcfc":{"name":"init(integerLiteral:)","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:SL1loiySbx_xtFZ":{"name":"<(_:_:)","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV6encode2toys7Encoder_p_tKF":{"name":"encode(to:)","abstract":"

Encode the wrapped value transparently to the given encoder.

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV4fromACyxGs7Decoder_p_tKcfc":{"name":"init(from:)","abstract":"

Decode a wrapped value from a decoder.

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV3maxACyxGvpZ":{"name":"max","abstract":"

The maximum representable integer in this type.

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV3minACyxGvpZ":{"name":"min","abstract":"

The minimum representable integer in this type.

","parent_name":"FixedSize"},"Structs/FixedSize.html#/s:13BinaryCodable9FixedSizeV4zeroACyxGvpZ":{"name":"zero","abstract":"

The zero value.

","parent_name":"FixedSize"},"Structs/FixedSize.html":{"name":"FixedSize","abstract":"

A wrapper for integer values which ensures that values are encoded in binary format using a fixed size.

"},"Structs/SignedValue.html":{"name":"SignedValue","abstract":"

A wrapper for integers more efficient for negative values.

"},"Protocols/SignedValueCompatible.html#/s:13BinaryCodable21SignedValueCompatibleP17positiveProtoTypeSSvp":{"name":"positiveProtoType","parent_name":"SignedValueCompatible"},"Protocols/FixedSizeCompatible.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","abstract":"

The wire type of the type, which has a constant length

","parent_name":"FixedSizeCompatible"},"Protocols/FixedSizeCompatible.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","abstract":"

The protobuf type equivalent to the fixed size type

","parent_name":"FixedSizeCompatible"},"Protocols/FixedSizeCompatible.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","abstract":"

The value encoded as fixed size binary data

","parent_name":"FixedSizeCompatible"},"Protocols/FixedSizeCompatible.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","abstract":"

Decode the value from binary data.

","parent_name":"FixedSizeCompatible"},"Protocols.html#/s:13BinaryCodable13ProtobufOneOfP":{"name":"ProtobufOneOf","abstract":"

Add conformance to this protocol to enums which should be encoded as Protobuf Oneof values.

"},"Protocols/FixedSizeCompatible.html":{"name":"FixedSizeCompatible","abstract":"

An integer type which can be forced to use a fixed-length encoding instead of variable-length encoding.

"},"Protocols/SignedValueCompatible.html":{"name":"SignedValueCompatible","abstract":"

A signed integer which can be forced to use zig-zag encoding.

"},"Extensions/UInt64.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"UInt64"},"Extensions/UInt64.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"UInt64"},"Extensions/UInt64.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"UInt64"},"Extensions/UInt64.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"UInt64"},"Extensions/UInt32.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"UInt32"},"Extensions/UInt32.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"UInt32"},"Extensions/UInt32.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"UInt32"},"Extensions/UInt32.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"UInt32"},"Extensions/UInt.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"UInt"},"Extensions/UInt.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"UInt"},"Extensions/UInt.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"UInt"},"Extensions/UInt.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"UInt"},"Extensions/Int64.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"Int64"},"Extensions/Int64.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"Int64"},"Extensions/Int64.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"Int64"},"Extensions/Int64.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"Int64"},"Extensions/Int64.html#/s:s5Int64V13BinaryCodableE17positiveProtoTypeSSvp":{"name":"positiveProtoType","parent_name":"Int64"},"Extensions/Int32.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"Int32"},"Extensions/Int32.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"Int32"},"Extensions/Int32.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"Int32"},"Extensions/Int32.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"Int32"},"Extensions/Int32.html#/s:s5Int32V13BinaryCodableE17positiveProtoTypeSSvp":{"name":"positiveProtoType","parent_name":"Int32"},"Extensions/Int.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD8DataTypeAA0gH0OvpZ":{"name":"fixedSizeDataType","parent_name":"Int"},"Extensions/Int.html#/s:13BinaryCodable19FixedSizeCompatibleP14fixedProtoTypeSSvp":{"name":"fixedProtoType","parent_name":"Int"},"Extensions/Int.html#/s:13BinaryCodable19FixedSizeCompatibleP04fromcD04pathx10Foundation4DataV_Says9CodingKey_pGtKcfc":{"name":"init(fromFixedSize:path:)","parent_name":"Int"},"Extensions/Int.html#/s:13BinaryCodable19FixedSizeCompatibleP05fixedD7Encoded10Foundation4DataVvp":{"name":"fixedSizeEncoded","parent_name":"Int"},"Extensions/Int.html#/s:Si13BinaryCodableE17positiveProtoTypeSSvp":{"name":"positiveProtoType","parent_name":"Int"},"Extensions.html#/s:13BinaryCodable11DecodingKeyO":{"name":"DecodingKey"},"Extensions.html#/s:13BinaryCodable21MixedCodingKeyWrapperV":{"name":"MixedCodingKeyWrapper"},"Extensions.html#/s:13BinaryCodable15ProtoKeyWrapperV":{"name":"ProtoKeyWrapper"},"Extensions.html#/s:13BinaryCodable13IntKeyWrapperV":{"name":"IntKeyWrapper"},"Extensions.html#/s:Sb":{"name":"Bool"},"Extensions.html#/s:10Foundation4DataV":{"name":"Data"},"Extensions.html#/s:Sf":{"name":"Float"},"Extensions/Int.html":{"name":"Int"},"Extensions/Int32.html":{"name":"Int32"},"Extensions/Int64.html":{"name":"Int64"},"Extensions.html#/s:SS":{"name":"String"},"Extensions/UInt.html":{"name":"UInt"},"Extensions/UInt32.html":{"name":"UInt32"},"Extensions/UInt64.html":{"name":"UInt64"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO36multipleValuesInSingleValueContaineryA2CmF":{"name":"multipleValuesInSingleValueContainer","abstract":"

A procedural error occuring during encoding.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO015noValueInSingleG9ContaineryA2CmF":{"name":"noValueInSingleValueContainer","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO21nilValuesNotSupportedyA2CmF":{"name":"nilValuesNotSupported","abstract":"

The encoded type contains optional values, which are not supported in the protocol buffer format.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO15unsupportedTypeyACSScACmF":{"name":"unsupportedType(_:)","abstract":"

The encoded type contains a basic type that is not supported.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO17superNotSupportedyA2CmF":{"name":"superNotSupported","abstract":"

Protocol buffers don’t support inheritance, so super can’t be encoded.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO17missingIntegerKeyyACSScACmF":{"name":"missingIntegerKey(_:)","abstract":"

The encoded type contains properties which don’t have an integer key.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO31multipleTypesInUnkeyedContaineryA2CmF":{"name":"multipleTypesInUnkeyedContainer","abstract":"

All values in unkeyed containers must have the same type.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO20integerKeyOutOfRangeyACSicACmF":{"name":"integerKeyOutOfRange(_:)","abstract":"

Field numbers must be positive integers not greater than 536870911 (2^29-1, or 0x1FFFFFFF)

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO26multipleContainersAccessedyA2CmF":{"name":"multipleContainersAccessed","abstract":"

Multiple calls to container<>(keyedBy:), unkeyedContainer(), or singleValueContainer() for an encoder.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO20noContainersAccessedyA2CmF":{"name":"noContainersAccessed","abstract":"

No calls to container<>(keyedBy:), unkeyedContainer(), or singleValueContainer() for an encoder.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO23rootIsNotKeyedContaineryA2CmF":{"name":"rootIsNotKeyedContainer","abstract":"

Protobuf requires an unkeyed container as the root node

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO13invalidAccessyACSScACmF":{"name":"invalidAccess(_:)","abstract":"

An unavailable encoding feature was accessed.

","parent_name":"ProtobufEncodingError"},"Enums/ProtobufEncodingError.html#/s:13BinaryCodable21ProtobufEncodingErrorO29protobufDefinitionUnavailableyACSScACmF":{"name":"protobufDefinitionUnavailable(_:)","parent_name":"ProtobufEncodingError"},"Enums/ProtobufDecodingError.html#/s:13BinaryCodable21ProtobufDecodingErrorO23unexpectedDictionaryKeyyA2CmF":{"name":"unexpectedDictionaryKey","parent_name":"ProtobufDecodingError"},"Enums/ProtobufDecodingError.html#/s:13BinaryCodable21ProtobufDecodingErrorO17superNotSupportedyA2CmF":{"name":"superNotSupported","abstract":"

Protocol buffers don’t support inheritance, so super can’t be encoded.

","parent_name":"ProtobufDecodingError"},"Enums/ProtobufDecodingError.html#/s:13BinaryCodable21ProtobufDecodingErrorO15unsupportedTypeyACSScACmF":{"name":"unsupportedType(_:)","abstract":"

The encoded type contains a basic type that is not supported.

","parent_name":"ProtobufDecodingError"},"Enums/ProtobufDecodingError.html#/s:13BinaryCodable21ProtobufDecodingErrorO13invalidAccessyACSScACmF":{"name":"invalidAccess(_:)","abstract":"

A decoding feature was accessed which is not supported for protobuf encoding.

","parent_name":"ProtobufDecodingError"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO21variableLengthIntegeryA2CmF":{"name":"variableLengthInteger","abstract":"

An integer value encoded as a Base128 Varint.","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO4byteyA2CmF":{"name":"byte","abstract":"

The value is encoded as a single byte.

","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO8twoBytesyA2CmF":{"name":"twoBytes","abstract":"

The value is encoded as two bytes.

","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO14variableLengthyA2CmF":{"name":"variableLength","abstract":"

The value is encoded using first a length (as a UInt64 var-int) followed by the bytes.

","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO9fourBytesyA2CmF":{"name":"fourBytes","abstract":"

The value is encoded using four bytes.

","parent_name":"DataType"},"Enums/DataType.html#/s:13BinaryCodable8DataTypeO10eightBytesyA2CmF":{"name":"eightBytes","abstract":"

The value is encoded using eight bytes.

","parent_name":"DataType"},"Enums/DataType.html":{"name":"DataType","abstract":"

The data type specifying how a value is encoded on the wire.

"},"Enums/ProtobufDecodingError.html":{"name":"ProtobufDecodingError","abstract":"

An error produced while decoding binary data.

"},"Enums/ProtobufEncodingError.html":{"name":"ProtobufEncodingError","abstract":"

An error thrown when encoding a value using ProtobufEncoder.

"},"Classes/ProtobufEncoder.html#/s:13BinaryCodable15ProtobufEncoderC8userInfoSDys010CodingUserF3KeyVypGvp":{"name":"userInfo","abstract":"

Any contextual information set by the user for encoding.

","parent_name":"ProtobufEncoder"},"Classes/ProtobufEncoder.html#/s:13BinaryCodable15ProtobufEncoderCACycfc":{"name":"init()","abstract":"

Create a new binary encoder.

","parent_name":"ProtobufEncoder"},"Classes/ProtobufEncoder.html#/s:13BinaryCodable15ProtobufEncoderC6encodey10Foundation4DataVxKSERzlF":{"name":"encode(_:)","abstract":"

Encode a value to binary data.

","parent_name":"ProtobufEncoder"},"Classes/ProtobufEncoder.html#/s:13BinaryCodable15ProtobufEncoderC6encodey10Foundation4DataVxKSERzlFZ":{"name":"encode(_:)","abstract":"

Encode a single value to binary data using a default encoder.

","parent_name":"ProtobufEncoder"},"Classes/ProtobufDecoder.html#/s:13BinaryCodable15ProtobufDecoderC8userInfoSDys010CodingUserF3KeyVypGvp":{"name":"userInfo","abstract":"

Any contextual information set by the user for decoding.

","parent_name":"ProtobufDecoder"},"Classes/ProtobufDecoder.html#/s:13BinaryCodable15ProtobufDecoderCACycfc":{"name":"init()","abstract":"

Create a new binary encoder.

","parent_name":"ProtobufDecoder"},"Classes/ProtobufDecoder.html#/s:13BinaryCodable15ProtobufDecoderC6decode_4fromxxm_10Foundation4DataVtKSeRzlF":{"name":"decode(_:from:)","abstract":"

Decode a type from binary data.

","parent_name":"ProtobufDecoder"},"Classes/ProtobufDecoder.html#/s:13BinaryCodable15ProtobufDecoderC6decode_4fromxxm_10Foundation4DataVtKSeRzlFZ":{"name":"decode(_:from:)","abstract":"

Decode a single value from binary data using a default decoder.

","parent_name":"ProtobufDecoder"},"Classes/BinaryStreamEncoder.html#/s:13BinaryCodable0A13StreamEncoderC7encoderACyxGAA0aD0C_tcfc":{"name":"init(encoder:)","abstract":"

Create a new stream encoder.

","parent_name":"BinaryStreamEncoder"},"Classes/BinaryStreamEncoder.html#/s:13BinaryCodable0A13StreamEncoderC6encodey10Foundation4DataVxKF":{"name":"encode(_:)","abstract":"

Encode an element for the data stream.

","parent_name":"BinaryStreamEncoder"},"Classes/BinaryStreamEncoder.html#/s:13BinaryCodable0A13StreamEncoderC6encode10contentsOf10Foundation4DataVqd___tK7ElementQyd__RszSTRd__lF":{"name":"encode(contentsOf:)","abstract":"

Encode a sequence of elements.

","parent_name":"BinaryStreamEncoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC7decoderACyxGAA0aD0C_tcfc":{"name":"init(decoder:)","abstract":"

Create a stream decoder.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC3addyy10Foundation4DataVF":{"name":"add(_:)","abstract":"

Add new data to the internal buffer without attempting to decode elements.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC12discardBytesyS2iF":{"name":"discardBytes(_:)","abstract":"

Discard bytes from the currently remaining bytes.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC6decode_25returnElementsBeforeErrorSayxG10Foundation4DataV_SbtKF":{"name":"decode(_:returnElementsBeforeError:)","abstract":"

Read elements from the stream until no more bytes are available.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC24decodeElementsUntilErrorSayxG8elements_s0H0_pSg5errortyF":{"name":"decodeElementsUntilError()","abstract":"

Read elements from the stream until an error occurs.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC14decodeElementsSayxGyKF":{"name":"decodeElements()","abstract":"

Read elements until no more data is available.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryStreamDecoder.html#/s:13BinaryCodable0A13StreamDecoderC13decodeElementxSgyKF":{"name":"decodeElement()","abstract":"

Attempt to decode a single element from the stream.

","parent_name":"BinaryStreamDecoder"},"Classes/BinaryFileEncoder.html#/s:13BinaryCodable0A11FileEncoderC6fileAt7encoderACyxG10Foundation3URLV_AA0aD0CtKcfc":{"name":"init(fileAt:encoder:)","abstract":"

Create a new file encoder.

","parent_name":"BinaryFileEncoder"},"Classes/BinaryFileEncoder.html#/s:13BinaryCodable0A11FileEncoderC5closeyyKF":{"name":"close()","abstract":"

Close the file.

","parent_name":"BinaryFileEncoder"},"Classes/BinaryFileEncoder.html#/s:13BinaryCodable0A11FileEncoderC5writeyyxKF":{"name":"write(_:)","abstract":"

Write a single element to the file.

","parent_name":"BinaryFileEncoder"},"Classes/BinaryFileEncoder.html#/s:13BinaryCodable0A11FileEncoderC5write10contentsOfyqd___tK7ElementQyd__RszSTRd__lF":{"name":"write(contentsOf:)","abstract":"

Write a sequence of elements to the file.

","parent_name":"BinaryFileEncoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC6fileAt7decoderACyxG10Foundation3URLV_AA0aD0CtKcfc":{"name":"init(fileAt:decoder:)","abstract":"

Create a file decoder.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC5closeyyKF":{"name":"close()","abstract":"

Close the file.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC4readyyyxKXEKF":{"name":"read(_:)","abstract":"

Read all elements in the file, and handle each element using a closure.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC7readAllSayxGyKF":{"name":"readAll()","abstract":"

Read all elements at once.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC17readAllUntilErrorSayxGyF":{"name":"readAllUntilError()","abstract":"

Read all elements at once, and ignore errors.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryFileDecoder.html#/s:13BinaryCodable0A11FileDecoderC11readElementxSgyKF":{"name":"readElement()","abstract":"

Read a single elements from the current position in the file.

","parent_name":"BinaryFileDecoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC22sortKeysDuringEncodingSbvp":{"name":"sortKeysDuringEncoding","abstract":"

Sort keyed data in the binary representation.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC38prependNilIndexSetForUnkeyedContainersSbvp":{"name":"prependNilIndexSetForUnkeyedContainers","abstract":"

Add a set of indices for nil values in unkeyed containers.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC8userInfoSDys010CodingUserE3KeyVypGvp":{"name":"userInfo","abstract":"

Any contextual information set by the user for encoding.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderCACycfc":{"name":"init()","abstract":"

Create a new binary encoder.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC6encodey10Foundation4DataVSE_pKF":{"name":"encode(_:)","abstract":"

Encode a value to binary data.

","parent_name":"BinaryEncoder"},"Classes/BinaryEncoder.html#/s:13BinaryCodable0A7EncoderC6encodey10Foundation4DataVSE_pKFZ":{"name":"encode(_:)","abstract":"

Encode a single value to binary data using a default encoder.

","parent_name":"BinaryEncoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderC8userInfoSDys010CodingUserE3KeyVypGvp":{"name":"userInfo","abstract":"

Any contextual information set by the user for decoding.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderC39containsNilIndexSetForUnkeyedContainersSbvp":{"name":"containsNilIndexSetForUnkeyedContainers","abstract":"

Assumes that unkeyed containers are encoded using a set of indices for nil values.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderCACycfc":{"name":"init()","abstract":"

Create a new binary encoder.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderC6decode_4fromxxm_10Foundation4DataVtKSeRzlF":{"name":"decode(_:from:)","abstract":"

Decode a type from binary data.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html#/s:13BinaryCodable0A7DecoderC6decode_4fromxxm_10Foundation4DataVtKSeRzlFZ":{"name":"decode(_:from:)","abstract":"

Decode a single value from binary data using a default decoder.

","parent_name":"BinaryDecoder"},"Classes/BinaryDecoder.html":{"name":"BinaryDecoder","abstract":"

An encoder to convert binary data back to Codable objects.

"},"Classes/BinaryEncoder.html":{"name":"BinaryEncoder","abstract":"

An encoder to convert Codable objects to binary data.

"},"Classes/BinaryFileDecoder.html":{"name":"BinaryFileDecoder","abstract":"

Read elements from a binary file.

"},"Classes/BinaryFileEncoder.html":{"name":"BinaryFileEncoder","abstract":"

Encode a stream of elements to a binary file.

"},"Classes/BinaryStreamDecoder.html":{"name":"BinaryStreamDecoder","abstract":"

Decode elements from a byte stream.

"},"Classes/BinaryStreamEncoder.html":{"name":"BinaryStreamEncoder","abstract":"

Encode elements sequentially into a binary data stream.

"},"Classes/ProtobufDecoder.html":{"name":"ProtobufDecoder","abstract":"

An encoder to convert protobuf binary data back to Codable objects.

"},"Classes/ProtobufEncoder.html":{"name":"ProtobufEncoder","abstract":"

An encoder to convert Codable objects to protobuf binary data.

"},"binaryformat.html":{"name":"BinaryFormat"},"protobufsupport.html":{"name":"ProtobufSupport"},"Guides.html":{"name":"Guides","abstract":"

The following guides are available globally.

"},"Classes.html":{"name":"Classes","abstract":"

The following classes are available globally.

"},"Enums.html":{"name":"Enumerations","abstract":"

The following enumerations are available globally.

"},"Extensions.html":{"name":"Extensions","abstract":"

The following extensions are available globally.

"},"Protocols.html":{"name":"Protocols","abstract":"

The following protocols are available globally.

"},"Structs.html":{"name":"Structures","abstract":"

The following structures are available globally.

"}} \ No newline at end of file diff --git a/docs/undocumented.json b/docs/undocumented.json deleted file mode 100644 index da2d01c..0000000 --- a/docs/undocumented.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "warnings": [ - - ], - "source_directory": "/Users/ch/Projects/BinaryCodable" -} \ No newline at end of file diff --git a/generateDocumentation.sh b/generateDocumentation.sh deleted file mode 100644 index 2960aa2..0000000 --- a/generateDocumentation.sh +++ /dev/null @@ -1,30 +0,0 @@ -# This script attempts to automate the generation of the HTML documentation -# for this swift package. It relies on [jazzy](https://github.com/realm/jazzy). -# -# To use this script: -# Install chruby -# sudo gem instll chruby -# Install ruby-build -# brew install ruby-build -# Install ruby version and switch -# ruby-build 3.3.0 ~/.rubies/ruby-3.3.0 -# chruby ruby-3.3.0 -# Install jazzy -# gem install jazzy - -# Switch to the ruby environment where jazzy is installed -chruby ruby-3.3.0 - -# Run the generation using the .jazzy.yaml configuration file -jazzy - -# Copy the documentation badge so that it can be found using the relative link in the readme -# Note: The readme needs the assets to be in /assets, while the HTML docs need the assets in /docs/assets -mkdir -p docs/docs -cp docs/badge.svg docs/docs/badge.svg - -# Copy the remaining badges and logo -mkdir -p docs/assets -cp assets/* docs/assets/ - -echo 'Generation complete'