Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Stricten, test, and bugfix http-server #170

Merged
merged 4 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/http-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@
"@tbdex/http-client": "workspace:*",
"@tbdex/protocol": "workspace:*",
"@web5/dids": "0.2.2",
"cors": "2.8.5",
"cors": "^2.8.5",
"express": "4.18.2"
},
"devDependencies": {
"@types/chai": "4.3.6",
"@types/cors": "^2.8.17",
"@types/express": "4.17.17",
"@types/http-errors": "2.0.4",
"@types/mocha": "10.0.1",
"@types/node": "20.9.4",
"@types/sinon": "^17.0.3",
"chai": "4.3.10",
"rimraf": "5.0.1",
"sinon": "17.0.1",
"supertest": "6.3.3",
"typescript": "5.2.2"
},
Expand Down
58 changes: 0 additions & 58 deletions packages/http-server/src/fakes.ts

This file was deleted.

64 changes: 41 additions & 23 deletions packages/http-server/src/http-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
GetOfferingsCallback,
} from './types.js'

import type { Express } from 'express'
import type { Express, Request, Response } from 'express'

import express from 'express'
import cors from 'cors'

import { getExchanges, getOfferings, submitOrder, submitClose, createExchange } from './request-handlers/index.js'
import { jsonBodyParser } from './middleware/index.js'
import { fakeExchangesApi, fakeOfferingsApi } from './fakes.js'
import { InMemoryOfferingsApi } from './in-memory-offerings-api.js'
import { InMemoryExchangesApi } from './in-memory-exchanges-api.js'

/**
* Maps the requests to their respective callbacks handlers
Expand Down Expand Up @@ -72,8 +73,8 @@
constructor(opts?: NewHttpServerOptions) {
this.callbacks = {}

this.exchangesApi = opts?.exchangesApi ?? fakeExchangesApi
this.offeringsApi = opts?.offeringsApi ?? fakeOfferingsApi
this.exchangesApi = opts?.exchangesApi ?? new InMemoryExchangesApi()
this.offeringsApi = opts?.offeringsApi ?? new InMemoryOfferingsApi()
this.pfiDid = opts?.pfiDid ?? 'did:ex:pfi'

// initialize api here so that consumers can attach custom endpoints
Expand Down Expand Up @@ -139,25 +140,42 @@
listen(port: number | string, callback?: () => void) {
const { offeringsApi, exchangesApi, pfiDid } = this

this.api.post('/exchanges/:exchangeId/rfq', createExchange({
callback: this.callbacks['rfq'], offeringsApi, exchangesApi,
}))

this.api.post('/exchanges/:exchangeId/order', submitOrder({
callback: this.callbacks['order'], exchangesApi
}))

this.api.post('/exchanges/:exchangeId/close', submitClose({
callback: this.callbacks['close'], exchangesApi
}))

this.api.get('/exchanges', getExchanges({
callback: this.callbacks['exchanges'], exchangesApi, pfiDid
}))

this.api.get('/offerings', getOfferings({
callback: this.callbacks['offerings'], offeringsApi
}))
this.api.post('/exchanges/:exchangeId/rfq', (req: Request, res: Response) =>
createExchange(req, res, {
callback: this.callbacks['rfq'],
offeringsApi,
exchangesApi,
})
github-advanced-security[bot] marked this conversation as resolved.
Dismissed
Show resolved Hide resolved
)

this.api.post('/exchanges/:exchangeId/order', (req: Request, res: Response) =>
submitOrder(req, res, {
callback: this.callbacks['order'],
exchangesApi
})
)

this.api.post('/exchanges/:exchangeId/close', (req: Request, res: Response) =>
submitClose(req, res,{
callback: this.callbacks.close,
exchangesApi,
})
)

this.api.get('/exchanges', (req: Request, res: Response) =>
getExchanges(req, res, {
callback: this.callbacks.exchanges,
exchangesApi,
pfiDid,
})
github-advanced-security[bot] marked this conversation as resolved.
Dismissed
Show resolved Hide resolved
)

this.api.get('/offerings', (req, res) =>
getOfferings(req, res, {
callback: this.callbacks['offerings'],
offeringsApi
})
)

// TODO: support hostname and backlog arguments
return this.api.listen(port, callback)
Expand Down
92 changes: 92 additions & 0 deletions packages/http-server/src/in-memory-exchanges-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Exchange } from '@tbdex/protocol'
import { Message, Rfq, Quote, Order, OrderStatus, Close } from '@tbdex/protocol'
import { ExchangesApi, GetExchangesFilter } from './main.js'

/**
* An in-memory implementation of {@link ExchangesApi} for example and default purposes.
* InMemoryExchangesApi has additional methods {@link InMemoryExchangesApi.addMessage}
* and {@link InMemoryExchangesApi.clearMessages}
*/
export class InMemoryExchangesApi implements ExchangesApi {
/** Map from exchange_id to Exchange */
exchangeMessagesMap: Map<string, Exchange>

constructor() {
this.exchangeMessagesMap = new Map<string, Exchange>()
}

async getExchanges(opts?: { filter: GetExchangesFilter }): Promise<Exchange[]> {
if (opts === undefined || opts.filter === undefined) {
// In production, this should probably return an empty list.
// For example and testing purposes, we return all exchanges.

return Array.from(this.exchangeMessagesMap.values())
}

Check warning on line 24 in packages/http-server/src/in-memory-exchanges-api.ts

View check run for this annotation

Codecov / codecov/patch

packages/http-server/src/in-memory-exchanges-api.ts#L20-L24

Added lines #L20 - L24 were not covered by tests

const exchanges: Exchange[] = []
if (opts.filter.id) {
// filter has `id` and `from`

for (const id of opts.filter.id) {
const exchange = this.exchangeMessagesMap.get(id)
if (exchange?.rfq?.from === opts.filter.from) {
exchanges.push(exchange)
}

Check warning on line 34 in packages/http-server/src/in-memory-exchanges-api.ts

View check run for this annotation

Codecov / codecov/patch

packages/http-server/src/in-memory-exchanges-api.ts#L33-L34

Added lines #L33 - L34 were not covered by tests
}
} else {
// filter only has `from`
this.exchangeMessagesMap.forEach((exchange, _id) => {
// You definitely shouldn't use FakeExchangesApi in production.
diehuxx marked this conversation as resolved.
Show resolved Hide resolved
// This will get really slow
if (exchange?.rfq?.from === opts.filter.from) {
exchanges.push(exchange)
}
})
}

return exchanges
}

async getExchange(opts: { id: string} ): Promise<Exchange | undefined> {
const exchange = this.exchangeMessagesMap.get(opts.id)
return Promise.resolve(exchange)
}

async getRfq(opts: { exchangeId: string }): Promise<Rfq | undefined> {
const exchange = this.exchangeMessagesMap.get(opts.exchangeId)
return exchange?.rfq
}

async getQuote(opts: { exchangeId: string }): Promise<Quote | undefined> {
const exchange = this.exchangeMessagesMap.get(opts.exchangeId)
return Promise.resolve(exchange?.quote)
}

Check warning on line 63 in packages/http-server/src/in-memory-exchanges-api.ts

View check run for this annotation

Codecov / codecov/patch

packages/http-server/src/in-memory-exchanges-api.ts#L61-L63

Added lines #L61 - L63 were not covered by tests

async getOrder(opts: { exchangeId: string }): Promise<Order | undefined> {
const exchange = this.exchangeMessagesMap.get(opts.exchangeId)
return exchange?.order
}

Check warning on line 68 in packages/http-server/src/in-memory-exchanges-api.ts

View check run for this annotation

Codecov / codecov/patch

packages/http-server/src/in-memory-exchanges-api.ts#L66-L68

Added lines #L66 - L68 were not covered by tests

async getOrderStatuses(opts: { exchangeId: string }): Promise<OrderStatus[]> {
diehuxx marked this conversation as resolved.
Show resolved Hide resolved
const exchange = this.exchangeMessagesMap.get(opts.exchangeId)
if (exchange?.orderstatus === undefined) {
return []
}
return [exchange.orderstatus]
diehuxx marked this conversation as resolved.
Show resolved Hide resolved
}

Check warning on line 76 in packages/http-server/src/in-memory-exchanges-api.ts

View check run for this annotation

Codecov / codecov/patch

packages/http-server/src/in-memory-exchanges-api.ts#L71-L76

Added lines #L71 - L76 were not covered by tests

async getClose(opts: { exchangeId: string }): Promise<Close | undefined> {
const exchange = this.exchangeMessagesMap.get(opts.exchangeId)
return exchange?.close
}

Check warning on line 81 in packages/http-server/src/in-memory-exchanges-api.ts

View check run for this annotation

Codecov / codecov/patch

packages/http-server/src/in-memory-exchanges-api.ts#L79-L81

Added lines #L79 - L81 were not covered by tests

addMessage(message: Message): void {
const exchange = this.exchangeMessagesMap.get(message.exchangeId) ?? new Exchange()
exchange.addNextMessage(message)
this.exchangeMessagesMap.set(message.exchangeId, exchange)
}

clearMessages(): void {
this.exchangeMessagesMap = new Map<string, Exchange>()
}

Check warning on line 91 in packages/http-server/src/in-memory-exchanges-api.ts

View check run for this annotation

Codecov / codecov/patch

packages/http-server/src/in-memory-exchanges-api.ts#L90-L91

Added lines #L90 - L91 were not covered by tests
}
72 changes: 72 additions & 0 deletions packages/http-server/src/in-memory-offerings-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Offering } from '@tbdex/protocol'
import { GetOfferingsFilter, OfferingsApi } from './types.js'

/**
* An in-memory implementation of {@link OfferingsApi} for example and default purposes.
* InMemoryOfferingsApi has additional methods {@link InMemoryOfferingsApi.addOffering}
* and {@link InMemoryOfferingsApi.clearOfferings}
*/
export class InMemoryOfferingsApi implements OfferingsApi {
/** Map from offering_id to Offering */
offeringsMap: Map<string, Offering>

constructor() {
this.offeringsMap = new Map<string, Offering>()
}

/**
* Add a single offering
* @param offering - Offering to be added to the {@link offeringsMap}
*/
addOffering(offering: Offering): void {
this.offeringsMap.set(offering.metadata.id, offering)
}

/**
* Clear existing list offerings
*/
clearOfferings(): void {
this.offeringsMap.clear()
}

Check warning on line 30 in packages/http-server/src/in-memory-offerings-api.ts

View check run for this annotation

Codecov / codecov/patch

packages/http-server/src/in-memory-offerings-api.ts#L29-L30

Added lines #L29 - L30 were not covered by tests

/**
* Retrieve a single offering if found
* @param opts - Filter with id used to select an offering
* @returns An offering if one exists, else undefined
*/
async getOffering(opts: { id: string }): Promise<Offering | undefined>{
return this.offeringsMap.get(opts.id)
}

/**
*
* @param opts - Filter used to select offerings
* @returns A list of offerings matching the filter
*/
async getOfferings(opts?: { filter: GetOfferingsFilter }): Promise<Offering[]> {
const allOfferings = Array.from(this.offeringsMap.values())

if (opts?.filter === undefined || Object.values(opts.filter).every(v => v === undefined)) {
// If no filter is provided, return all offerings
return allOfferings
}

const { filter: {
id,
payinCurrency,
payoutCurrency,
payinMethodKind,
payoutMethodKind,
} } = opts

return allOfferings.filter((offering) => {
// If filter includes a field, make sure the returned offerings match
return (!id || id === offering.metadata.id) &&
(!payinCurrency || payinCurrency === offering.data.payinCurrency.currencyCode) &&
(!payoutCurrency || payoutCurrency === offering.data.payoutCurrency.currencyCode) &&
(!payinMethodKind || offering.data.payinMethods.map(pm => pm.kind).includes(payinMethodKind)) &&
(!payoutMethodKind || offering.data.payoutMethods.map(pm => pm.kind).includes(payoutMethodKind))
})
}

}
Loading
Loading