This repository has been archived by the owner on Dec 12, 2024. It is now read-only.
generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement tbDEX protocol parsing test vectors (#31)
- Loading branch information
Showing
22 changed files
with
509 additions
and
154 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[submodule "Tests/tbDEXTestVectors/tbdex-spec"] | ||
path = Tests/tbDEXTestVectors/tbdex-spec | ||
url = https://github.com/TBD54566975/tbdex |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,12 @@ | ||
bootstrap: | ||
# Initialize submodules | ||
git submodule update --init | ||
# Initialize sparse checkout in the `tbdex-spec` submodule | ||
git -C Tests/tbDEXTestVectors/tbdex-spec config core.sparseCheckout true | ||
# Sparse checkout only the `hosted/test-vectors` directory from `tbdex-spec` | ||
git -C Tests/tbDEXTestVectors/tbdex-spec sparse-checkout set hosted/test-vectors | ||
# Update submodules so they sparse checkout takes effect | ||
git submodule update | ||
|
||
format: | ||
swift format --in-place --recursive . |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,14 @@ | ||
# $PROJECT_NAME README | ||
# tbdex-swift | ||
|
||
Congrats, project leads! You got a new project to grow! | ||
WIP! | ||
|
||
This stub is meant to help you form a strong community around your work. It's yours to adapt, and may | ||
diverge from this initial structure. Just keep the files seeded in this repo, and the rest is yours to evolve! | ||
# Prerequisites | ||
|
||
## Introduction | ||
## Cloning | ||
|
||
Orient users to the project here. This is a good place to start with an assumption | ||
that the user knows very little - so start with the Big Picture and show how this | ||
project fits into it. It may be good to reference/link the broader architecture in the | ||
`collaboration` repo or the developer site here. | ||
After cloning this repository, run: | ||
``` | ||
make bootstrap | ||
``` | ||
|
||
Then maybe a dive into what this project does. | ||
|
||
Diagrams and other visuals are helpful here. Perhaps code snippets showing usage. | ||
|
||
Project leads should complete, alongside this `README`: | ||
* [CODEOWNERS](./CODEOWNERS) - set project lead(s) | ||
* [CONTRIBUTING.md](./CONTRIBUTING.md) - Fill out how to: install prereqs, build, test, run, access CI, chat, discuss, file issues | ||
* [Bug-report.md](.github/ISSUE_TEMPLATE/bug-report.md) - Fill out `Assignees` add codeowners @names | ||
* [config.yml](.github/ISSUE_TEMPLATE/config.yml) - remove "(/add your discord channel..)" and replace the url with your Discord channel if applicable | ||
|
||
The other files in this template repo may be used as-is: | ||
* [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) | ||
* [GOVERNANCE.md](./GOVERNANCE.md) | ||
* [LICENSE](./LICENSE) | ||
|
||
## Project Resources | ||
|
||
| Resource | Description | | ||
| ------------------------------------------ | ------------------------------------------------------------------------------ | | ||
| [CODEOWNERS](./CODEOWNERS) | Outlines the project lead(s) | | ||
| [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) | Expected behavior for project contributors, promoting a welcoming environment | | ||
| [CONTRIBUTING.md](./CONTRIBUTING.md) | Developer guide to build, test, run, access CI, chat, discuss, file issues | | ||
| [GOVERNANCE.md](./GOVERNANCE.md) | Project governance | | ||
| [LICENSE](./LICENSE) | Apache License, Version 2.0 | | ||
This will configure the repository's submodules properly, and ensure you're all set to go! |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import Foundation | ||
|
||
/// A date formatter that can be used to encode and decode dates in the ISO8601 format, | ||
/// compatible with the larger tbDEX ecosystem. | ||
let tbDEXDateFormatter: ISO8601DateFormatter = { | ||
let dateFormatter = ISO8601DateFormatter() | ||
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] | ||
return dateFormatter | ||
}() |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import Foundation | ||
|
||
public class tbDEXJSONDecoder: JSONDecoder { | ||
|
||
public override init() { | ||
super.init() | ||
|
||
dateDecodingStrategy = .custom { decoder in | ||
let container = try decoder.singleValueContainer() | ||
let dateString = try container.decode(String.self) | ||
|
||
if let date = tbDEXDateFormatter.date(from: dateString) { | ||
return date | ||
} else { | ||
throw DecodingError.dataCorruptedError( | ||
in: container, | ||
debugDescription: "Invalid date: \(dateString)" | ||
) | ||
} | ||
} | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import Foundation | ||
|
||
public class tbDEXJSONEncoder: JSONEncoder { | ||
|
||
public override init() { | ||
super.init() | ||
|
||
outputFormatting = .sortedKeys | ||
dateEncodingStrategy = .custom { date, encoder in | ||
var container = encoder.singleValueContainer() | ||
try container.encode(tbDEXDateFormatter.string(from: date)) | ||
} | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import AnyCodable | ||
import Foundation | ||
|
||
/// Enumeration that can represent any `Message` type. | ||
/// | ||
/// `AnyMessage` should be used in contexts when given a `Message`, but the exact type | ||
/// of the `Message` is unknown until runtime. | ||
/// | ||
/// Example: When calling an endpoint that returns `Message`s, but it's impossible to know exactly | ||
/// what kind of `Message` it is until the JSON response is parsed. | ||
public enum AnyMessage { | ||
case close(Close) | ||
case order(Order) | ||
case orderStatus(OrderStatus) | ||
case quote(Quote) | ||
case rfq(RFQ) | ||
|
||
/// Parse a JSON string into an `AnyMessage` object, which can represent any message type. | ||
/// - Parameter jsonString: A string containing a JSON representation of a `Message` | ||
/// - Returns: An `AnyMessage` object, representing the parsed JSON string | ||
public static func parse(_ jsonString: String) throws -> AnyMessage { | ||
guard let data = jsonString.data(using: .utf8) else { | ||
throw Error.invalidJSONString | ||
} | ||
|
||
return try tbDEXJSONDecoder().decode(AnyMessage.self, from: data) | ||
} | ||
} | ||
|
||
// MARK: - Decodable | ||
|
||
extension AnyMessage: Decodable { | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.singleValueContainer() | ||
|
||
// Read the JSON payload into a dictionary representation | ||
let messageJSONObject = try container.decode([String: AnyCodable].self) | ||
|
||
// Ensure that a metadata object is present within the JSON payload | ||
guard let metadataJSONObject = messageJSONObject["metadata"]?.value as? [String: Any] else { | ||
throw DecodingError.valueNotFound( | ||
AnyMessage.self, | ||
DecodingError.Context( | ||
codingPath: decoder.codingPath, | ||
debugDescription: "metadata not found" | ||
) | ||
) | ||
} | ||
|
||
// Decode the metadata into a strongly-typed `MessageMetadata` object | ||
let metadataData = try JSONSerialization.data(withJSONObject: metadataJSONObject) | ||
let metadata = try tbDEXJSONDecoder().decode(MessageMetadata.self, from: metadataData) | ||
|
||
// Decode the message itself into it's strongly-typed representation, indicated by the `metadata.kind` field | ||
switch metadata.kind { | ||
case .close: | ||
self = .close(try container.decode(Close.self)) | ||
case .order: | ||
self = .order(try container.decode(Order.self)) | ||
case .orderStatus: | ||
self = .orderStatus(try container.decode(OrderStatus.self)) | ||
case .quote: | ||
self = .quote(try container.decode(Quote.self)) | ||
case .rfq: | ||
self = .rfq(try container.decode(RFQ.self)) | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Errors | ||
|
||
extension AnyMessage { | ||
|
||
public enum Error: Swift.Error { | ||
/// The provided JSON string is invalid | ||
case invalidJSONString | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import AnyCodable | ||
import Foundation | ||
|
||
/// Enumeration that can represent any `Resource` type. | ||
/// | ||
/// `AnyResource` should be used in contexts when given a `Resource`, but the exact type | ||
/// of the `Resource` is unknown until runtime. | ||
/// | ||
/// Example: When calling an endpoint that returns `Resource`s, but it's impossible to know exactly | ||
/// what kind of `Resource` it is until the JSON response is parsed. | ||
public enum AnyResource { | ||
case offering(Offering) | ||
|
||
public static func parse(_ jsonString: String) throws -> AnyResource { | ||
guard let data = jsonString.data(using: .utf8) else { | ||
throw Error.invalidJSONString | ||
} | ||
|
||
return try tbDEXJSONDecoder().decode(AnyResource.self, from: data) | ||
} | ||
} | ||
|
||
// MARK: - Decodable | ||
|
||
extension AnyResource: Decodable { | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.singleValueContainer() | ||
|
||
// Read the JSON payload into a dictionary representation | ||
let resourceJSONObject = try container.decode([String: AnyCodable].self) | ||
|
||
// Ensure that a metadata object is present within the JSON payload | ||
guard let metadataJSONObject = resourceJSONObject["metadata"]?.value as? [String: Any] else { | ||
throw DecodingError.valueNotFound( | ||
AnyResource.self, | ||
DecodingError.Context( | ||
codingPath: decoder.codingPath, | ||
debugDescription: "metadata not found" | ||
) | ||
) | ||
} | ||
|
||
// Decode the metadata into a strongly-typed `ResourceMetadata` object | ||
let metadataData = try JSONSerialization.data(withJSONObject: metadataJSONObject) | ||
let metadata = try tbDEXJSONDecoder().decode(ResourceMetadata.self, from: metadataData) | ||
|
||
// Decode the resource itself into it's strongly-typed representation, indicated by the `metadata.kind` field | ||
switch metadata.kind { | ||
case .offering: | ||
self = .offering(try container.decode(Offering.self)) | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Errors | ||
|
||
extension AnyResource { | ||
|
||
enum Error: Swift.Error { | ||
/// The provided JSON string is invalid | ||
case invalidJSONString | ||
} | ||
} |
Oops, something went wrong.