Created
June 22, 2025 19:48
-
-
Save thecoolwinter/50374d379c7d3f01a77144145c67d9bf to your computer and use it in GitHub Desktop.
Hashable Encodable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// HashableEncoder.swift | |
// Anchor | |
// | |
// Copyright (C) 2025 Khan Winter | |
// | |
import Foundation | |
import Crypto | |
struct HashableEncoder<Hash: HashFunction> { | |
private var context: Context<Hash> | |
init(hashFunction: Hash) { | |
self.context = Context(hashFunction: hashFunction) | |
} | |
func hash<S>(_ value: S) throws where S: Encodable { | |
try InternalContainer<Void, Hash>(context: context).encode(value) | |
} | |
func hash(_ value: any Encodable) throws { | |
try InternalContainer<Void, Hash>(context: context).encode(value) | |
} | |
consuming func finalize() -> Hash.Digest { | |
context.hashFunction.finalize() | |
} | |
} | |
fileprivate class Context<Hash: HashFunction> { | |
var hashFunction: Hash | |
init(hashFunction: Hash) { | |
self.hashFunction = hashFunction | |
} | |
} | |
fileprivate struct InternalContainer< | |
Generic, | |
Hash: HashFunction | |
>: Encoder, SingleValueEncodingContainer, UnkeyedEncodingContainer { | |
private var context: Context<Hash> | |
var count: Int = 0 | |
var codingPath: [any CodingKey] = [] | |
var userInfo: [CodingUserInfoKey : Any] = [:] | |
init(context: Context<Hash>) { | |
self.context = context | |
} | |
func encode<T>(_ value: T) throws where T : Encodable { try value.encode(to: self) } | |
func encodeNil() throws { } | |
func encode(_ value: Bool) throws { context.hashFunction.combine(value) } | |
func encode(_ value: Double) throws { context.hashFunction.combine(value) } | |
func encode(_ value: Float) throws { context.hashFunction.combine(value) } | |
func encode<T>(_ value: T) throws where T: Encodable, T: FixedWidthInteger { | |
context.hashFunction.combine(value) | |
} | |
func encode(_ value: String) throws { | |
guard let data = value.data(using: .utf8) else { | |
throw EncodingError.invalidValue( | |
value, | |
.init(codingPath: [], debugDescription: "Failed to encode string to utf8 for hashing") | |
) | |
} | |
context.hashFunction.update(data: data) | |
} | |
func encode<T>(_ value: T) throws where T: Encodable, T: DataProtocol { | |
context.hashFunction.update(data: value) | |
} | |
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey { | |
.init(InternalContainer<Key, Hash>(context: context)) | |
} | |
func unkeyedContainer() -> any UnkeyedEncodingContainer { InternalContainer(context: context) } | |
func singleValueContainer() -> any SingleValueEncodingContainer { self } | |
func nestedContainer<NestedKey>( | |
keyedBy keyType: NestedKey.Type | |
) -> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey { | |
.init(InternalContainer<NestedKey, Hash>(context: context)) | |
} | |
func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { self } | |
func superEncoder() -> any Encoder { self } | |
} | |
extension InternalContainer: KeyedEncodingContainerProtocol where Generic: CodingKey { | |
typealias Key = Generic | |
func encodeNil(forKey key: Key) throws { } | |
func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable { | |
try value.encode(to: InternalContainer<Void, Hash>(context: context)) | |
} | |
func superEncoder() -> Encoder { | |
InternalContainer(context: context) | |
} | |
func superEncoder(forKey key: Key) -> Encoder { | |
InternalContainer(context: context) | |
} | |
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { | |
InternalContainer(context: context) | |
} | |
func nestedContainer<NestedKey>( | |
keyedBy keyType: NestedKey.Type, | |
forKey key: Key | |
) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey { | |
InternalContainer(context: context).container(keyedBy: NestedKey.self) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is an example implementation of an
Encoder
that hashesEncodable
types. It's incomplete with a missing extension to the Crypto library'sHashFunction
, and it's actually not reliable since it doesn't guarantee property ordering but I didn't want to waste the effort I put in learning theEncoder
API.