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

Commit

Permalink
#196 Restructure http endpoints according to latest design (#204)
Browse files Browse the repository at this point in the history
* Update existing endpoints in server

* Update client to new endpoints

* Implement getExchange server endpoint

* Lint

* Woops use PUT

* Changeset

* fix test

* tbdocs

* Rename SubmitRfqCallback -> CreateExchangeCallback
  • Loading branch information
Diane Huxley authored Mar 20, 2024
1 parent e449a21 commit 69d10f0
Show file tree
Hide file tree
Showing 15 changed files with 465 additions and 247 deletions.
6 changes: 6 additions & 0 deletions .changeset/rotten-goats-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tbdex/http-client": minor
"@tbdex/http-server": minor
---

Restructure HTTP exchange endpoints
12 changes: 6 additions & 6 deletions packages/http-client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ export class TbdexHttpClient {
static async createExchange(rfq: Rfq, opts?: { replyTo?: string }): Promise<void> {
await rfq.verify()

const { to: pfiDid, exchangeId } = rfq.metadata
const { to: pfiDid } = rfq.metadata
const requestBody = JSON.stringify({ rfq, replyTo: opts?.replyTo })

await TbdexHttpClient.sendMessage(pfiDid, `/exchanges/${exchangeId}/rfq`, requestBody)
await TbdexHttpClient.sendMessage(pfiDid, 'POST', `/exchanges`, requestBody)
}

/**
Expand All @@ -88,7 +88,7 @@ export class TbdexHttpClient {
const { to: pfiDid, exchangeId } = order.metadata
const requestBody = JSON.stringify(order)

await TbdexHttpClient.sendMessage(pfiDid, `/exchanges/${exchangeId}/order`, requestBody)
await TbdexHttpClient.sendMessage(pfiDid, 'PUT', `/exchanges/${exchangeId}`, requestBody)
}

/**
Expand All @@ -105,17 +105,17 @@ export class TbdexHttpClient {

const requestBody = JSON.stringify(close)

await TbdexHttpClient.sendMessage(pfiDid, `/exchanges/${exchangeId}/close`, requestBody)
await TbdexHttpClient.sendMessage(pfiDid, 'PUT', `/exchanges/${exchangeId}`, requestBody)
}

private static async sendMessage(pfiDid: string, path: string, requestBody: string): Promise<void> {
private static async sendMessage(pfiDid: string, verb: 'GET' | 'PUT' | 'POST', path: string, requestBody: string): Promise<void> {
const pfiServiceEndpoint = await TbdexHttpClient.getPfiServiceEndpoint(pfiDid)
const apiRoute = `${pfiServiceEndpoint}${path}`

let response: Response
try {
response = await fetch(apiRoute, {
method : 'POST',
method : verb,
headers : { 'content-type': 'application/json' },
body : requestBody
})
Expand Down
6 changes: 3 additions & 3 deletions packages/http-client/tests/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('client', () => {
expect(e.statusCode).to.exist
expect(e.details).to.exist
expect(e.recipientDid).to.equal(pfiDid.uri)
expect(e.url).to.equal(`https://localhost:9000/exchanges/${rfq.metadata.exchangeId}/rfq`)
expect(e.url).to.equal(`https://localhost:9000/exchanges`)
}
})

Expand Down Expand Up @@ -170,7 +170,7 @@ describe('client', () => {
expect(e.statusCode).to.exist
expect(e.details).to.exist
expect(e.recipientDid).to.equal(pfiDid.uri)
expect(e.url).to.equal(`https://localhost:9000/exchanges/${order.metadata.exchangeId}/order`)
expect(e.url).to.equal(`https://localhost:9000/exchanges/${order.metadata.exchangeId}`)
}
})

Expand Down Expand Up @@ -256,7 +256,7 @@ describe('client', () => {
expect(e.statusCode).to.exist
expect(e.details).to.exist
expect(e.recipientDid).to.equal(pfiDid.uri)
expect(e.url).to.equal(`https://localhost:9000/exchanges/${close.metadata.exchangeId}/close`)
expect(e.url).to.equal(`https://localhost:9000/exchanges/${close.metadata.exchangeId}`)
}
})

Expand Down
71 changes: 43 additions & 28 deletions packages/http-server/src/http-server.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
import type {
OfferingsApi,
ExchangesApi,
SubmitRfqCallback,
CreateExchangeCallback,
SubmitOrderCallback,
SubmitCloseCallback,
GetExchangesCallback,
GetOfferingsCallback,
GetExchangeCallback,
} from './types.js'

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 { getExchanges, getOfferings, createExchange } from './request-handlers/index.js'
import { jsonBodyParser } from './middleware/index.js'
import { InMemoryOfferingsApi } from './in-memory-offerings-api.js'
import { InMemoryExchangesApi } from './in-memory-exchanges-api.js'
import { submitMessage } from './request-handlers/submit-message.js'
import { getExchange } from './request-handlers/get-exchange.js'

/**
* Maps the requests to their respective callbacks handlers
* @beta
*/
type CallbackMap = {
exchanges?: GetExchangesCallback
offerings?: GetOfferingsCallback
rfq?: SubmitRfqCallback
order?: SubmitOrderCallback
close?: SubmitCloseCallback
getExchange?: GetExchangeCallback
getExchanges?: GetExchangesCallback
getOfferings?: GetOfferingsCallback
createExchange?: CreateExchangeCallback
submitOrder?: SubmitOrderCallback
submitClose?: SubmitCloseCallback
}

/**
Expand Down Expand Up @@ -92,26 +96,35 @@ export class TbdexHttpServer {
* @param callback - A callback to be invoked when a valid Rfq is sent to the
* CreateExchange endpoint.
*/
onSubmitRfq(callback: SubmitRfqCallback): void {
this.callbacks.rfq = callback
onCreateExchange(callback: CreateExchangeCallback): void {
this.callbacks.createExchange = callback
}

/**
* Set up a callback or overwrite the existing callback for the for the SubmitOrder endpoint
* Set up a callback or overwrite the existing callback for the for the SubmitMessage endpoint
* @param callback - A callback to be invoked when a valid Order is sent to the
* SubmitOrder endpoint.
* SubmitMessage endpoint.
*/
onSubmitOrder(callback: SubmitOrderCallback): void {
this.callbacks.order = callback
this.callbacks.submitOrder = callback
}

/**
* Set up a callback or overwrite the existing callback for the SubmitClose endpoint.
* Set up a callback or overwrite the existing callback for the for the SubmitMessage endpoint
* @param callback - A callback to be invoked when a valid Close is sent to the
* SubmitClose endpoint.
* SubmitMessage endpoint.
*/
onSubmitClose(callback: SubmitCloseCallback): void {
this.callbacks.close = callback
this.callbacks.submitClose = callback
}

/**
* Set up a callback or overwrite the existing callback for the GetExchange endpoint
* @param callback - A callback to be invoked when a valid request is sent to the
* GetExchange endpoint.
*/
onGetExchange(callback: GetExchangeCallback): void {
this.callbacks.getExchange = callback
}

/**
Expand All @@ -120,7 +133,7 @@ export class TbdexHttpServer {
* GetExchanges endpoint.
*/
onGetExchanges(callback: GetExchangesCallback): void {
this.callbacks.exchanges = callback
this.callbacks.getExchanges = callback
}

/**
Expand All @@ -129,7 +142,7 @@ export class TbdexHttpServer {
* GetOfferings endpoint.
*/
onGetOfferings(callback: GetOfferingsCallback): void {
this.callbacks.offerings = callback
this.callbacks.getOfferings = callback
}

/**
Expand All @@ -140,39 +153,41 @@ export class TbdexHttpServer {
listen(port: number | string, callback?: () => void) {
const { offeringsApi, exchangesApi, pfiDid } = this

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

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

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

this.api.get('/exchanges', (req: Request, res: Response) =>
getExchanges(req, res, {
callback: this.callbacks.exchanges,
callback: this.callbacks.getExchanges,
exchangesApi,
pfiDid,
})
)

this.api.get('/offerings', (req, res) =>
getOfferings(req, res, {
callback: this.callbacks['offerings'],
callback: this.callbacks['getOfferings'],
offeringsApi
})
)
Expand Down
4 changes: 2 additions & 2 deletions packages/http-server/src/request-handlers/create-exchange.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { OfferingsApi, ExchangesApi, SubmitRfqCallback } from '../types.js'
import type { OfferingsApi, ExchangesApi, CreateExchangeCallback } from '../types.js'
import { Rfq } from '@tbdex/protocol'
import type { ErrorDetail } from '@tbdex/http-client'

import { CallbackError } from '../callback-error.js'
import { Request, Response } from 'express'

type CreateExchangeOpts = {
callback?: SubmitRfqCallback
callback?: CreateExchangeCallback
offeringsApi: OfferingsApi
exchangesApi: ExchangesApi
}
Expand Down
53 changes: 53 additions & 0 deletions packages/http-server/src/request-handlers/get-exchange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { ExchangesApi, GetExchangeCallback } from '../types.js'
import { TbdexHttpClient } from '@tbdex/http-client'
import { Request, Response } from 'express'

type GetExchangeOpts = {
callback?: GetExchangeCallback
exchangesApi: ExchangesApi
pfiDid: string
}

export async function getExchange(request: Request, response: Response, opts: GetExchangeOpts): Promise<void> {
const { callback, exchangesApi, pfiDid } = opts

const authzHeader = request.headers['authorization']
if (!authzHeader) {
response.status(401).json({ errors: [{ detail: 'Authorization header required' }] })
return
}

const [_, requestToken] = authzHeader.split('Bearer ')

if (!requestToken) {
response.status(401).json({ errors: [{ detail: 'Malformed Authorization header. Expected: Bearer TOKEN_HERE' }] })
return
}

let requesterDid: string
try {
requesterDid = await TbdexHttpClient.verifyRequestToken({ requestToken: requestToken, pfiDid })
} catch(e) {
response.status(401).json({ errors: [{ detail: `Malformed Authorization header: ${e}` }] })
return
}

const exchange = await exchangesApi.getExchange({ id: request.params.exchangeId })
if (exchange === undefined) {
response.status(404).json({ errors: [{ detail: `No exchange found with exchangeId ${request.params.exchangeId}` }] })
return
} else {
if (exchange.rfq!.metadata.from !== requesterDid) {
response.status(403).json({ errors: [{ detail: `Forbidden` }] })
return
}
}

if (callback) {
// TODO: figure out what to do with callback result. should we pass through the exchanges we've fetched
// and allow the callback to modify what's returned? (issue #10)
const _result = await callback({ request, response })
}

response.status(200).json({ data: exchange })
}
14 changes: 1 addition & 13 deletions packages/http-server/src/request-handlers/submit-close.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,9 @@ type SubmitCloseOpts = {
exchangesApi: ExchangesApi
}

export async function submitClose(req: Request, res: Response, opts: SubmitCloseOpts): Promise<void> {
export async function submitClose(close: Close, req: Request, res: Response, opts: SubmitCloseOpts): Promise<void> {
const { callback, exchangesApi } = opts

let close: Close

try {
close = await Close.parse(req.body)
} catch(e) {
const errorResponse: ErrorDetail = { detail: 'Request body was not a valid Close message' }
res.status(400).json({ errors: [errorResponse] })
return
}

// Ensure that an exchange exists to be closed
const exchange = await exchangesApi.getExchange({ id: close.exchangeId })

Expand All @@ -33,8 +23,6 @@ export async function submitClose(req: Request, res: Response, opts: SubmitClose
return
}

console.log('exchange.isValidNext(close.metadata.kind)::::', exchange.isValidNext(close.metadata.kind))

// Ensure this exchange can be Closed
if(!exchange.isValidNext(close.metadata.kind)) {
const errorResponse: ErrorDetail = {
Expand Down
48 changes: 48 additions & 0 deletions packages/http-server/src/request-handlers/submit-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { ExchangesApi, SubmitCloseCallback, SubmitOrderCallback } from '../types.js'
import type { ErrorDetail } from '@tbdex/http-client'
import { Message } from '@tbdex/protocol'

import { Request, Response } from 'express'
import { submitOrder } from './submit-order.js'
import { submitClose } from './submit-close.js'
import { Parser } from '@tbdex/protocol'

type SubmitMessageOpts = {
submitOrderCallback?: SubmitOrderCallback
submitCloseCallback?: SubmitCloseCallback
exchangesApi: ExchangesApi
}

export async function submitMessage(req: Request, res: Response, opts: SubmitMessageOpts): Promise<void> {
let message: Message

try {
message = await Parser.parseMessage(req.body)
} catch(e) {
const errorResponse: ErrorDetail = { detail: 'Request body was not a valid Order or Close message' }
res.status(400).json({ errors: [errorResponse] })
return
}

if (message.metadata.exchangeId !== req.params.exchangeId) {
const errorResponse: ErrorDetail = { detail: 'ExchangeId in message did not match exchangeId in path' }
res.status(400).json({ errors: [errorResponse] })
return
}

if (message.isOrder()) {
await submitOrder(message, req, res, {
callback : opts.submitOrderCallback,
exchangesApi : opts.exchangesApi,
})
} else if (message.isClose()) {
await submitClose(message, req, res, {
callback : opts.submitCloseCallback,
exchangesApi : opts.exchangesApi,
})
} else {
const errorResponse: ErrorDetail = { detail: 'Request body was not a valid Order or Close message' }
res.status(400).json({ errors: [errorResponse] })
return
}
}
Loading

0 comments on commit 69d10f0

Please sign in to comment.