Add get active slot /slots/{slotId} to REST api, use utils/json (#645)
* Add get active slot /slots/{slotId} to REST api, use utils/json - Add endpoint /slots/{slotId} to get an active SalesAgent from the Sales module. Used in integration tests to test when a sale has reached a certain state. Those integration test changes will be included in a larger PR, coming later. - Add OpenAPI changes for new endpoint and associated components - Use utils/json instead of nim-json-serialization. Required exemption of imports from several packages that export nim-json-serialization by default. * Only except `toJson` from import/export of chronicles
This commit is contained in:
parent
86a6ef9215
commit
3907ca4095
|
@ -7,7 +7,7 @@ import ../clock
|
||||||
import ./interactions
|
import ./interactions
|
||||||
|
|
||||||
export purchasing
|
export purchasing
|
||||||
export chronicles
|
export chronicles except toJson
|
||||||
|
|
||||||
type
|
type
|
||||||
ClientInteractions* = ref object of ContractInteractions
|
ClientInteractions* = ref object of ContractInteractions
|
||||||
|
|
|
@ -5,7 +5,7 @@ import ../../sales
|
||||||
import ./interactions
|
import ./interactions
|
||||||
|
|
||||||
export sales
|
export sales
|
||||||
export chronicles
|
export chronicles except toJson
|
||||||
|
|
||||||
type
|
type
|
||||||
HostInteractions* = ref object of ContractInteractions
|
HostInteractions* = ref object of ContractInteractions
|
||||||
|
|
|
@ -8,8 +8,8 @@ import ./requests
|
||||||
import ./config
|
import ./config
|
||||||
|
|
||||||
export stint
|
export stint
|
||||||
export ethers
|
export ethers except `%`, `%*`, toJson
|
||||||
export erc20
|
export erc20 except `%`, `%*`, toJson
|
||||||
export config
|
export config
|
||||||
export requests
|
export requests
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,10 @@ proc fromHex*[T: distinct](_: type T, hex: string): T =
|
||||||
type baseType = T.distinctBase
|
type baseType = T.distinctBase
|
||||||
T baseType.fromHex(hex)
|
T baseType.fromHex(hex)
|
||||||
|
|
||||||
|
proc toHex*[T: distinct](id: T): string =
|
||||||
|
type baseType = T.distinctBase
|
||||||
|
baseType(id).toHex
|
||||||
|
|
||||||
func fromTuple(_: type StorageRequest, tupl: tuple): StorageRequest =
|
func fromTuple(_: type StorageRequest, tupl: tuple): StorageRequest =
|
||||||
StorageRequest(
|
StorageRequest(
|
||||||
client: tupl[0],
|
client: tupl[0],
|
||||||
|
|
|
@ -16,11 +16,10 @@ import std/sequtils
|
||||||
|
|
||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
import pkg/questionable/results
|
import pkg/questionable/results
|
||||||
import pkg/chronicles
|
import pkg/chronicles except toJson
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/presto
|
import pkg/presto except toJson
|
||||||
import pkg/libp2p
|
import pkg/metrics except toJson
|
||||||
import pkg/metrics
|
|
||||||
import pkg/stew/base10
|
import pkg/stew/base10
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import pkg/confutils
|
import pkg/confutils
|
||||||
|
@ -32,8 +31,10 @@ import pkg/codexdht/discv5/spr as spr
|
||||||
import ../node
|
import ../node
|
||||||
import ../blocktype
|
import ../blocktype
|
||||||
import ../conf
|
import ../conf
|
||||||
import ../contracts except `%*`, `%` # imported from contracts/marketplace (exporting ethers)
|
import ../contracts
|
||||||
import ../streams
|
import ../manifest
|
||||||
|
import ../streams/asyncstreamwrapper
|
||||||
|
import ../stores/blockstore
|
||||||
|
|
||||||
import ./coders
|
import ./coders
|
||||||
import ./json
|
import ./json
|
||||||
|
@ -197,6 +198,29 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) =
|
||||||
trace "Excepting processing request", exc = exc.msg
|
trace "Excepting processing request", exc = exc.msg
|
||||||
return RestApiResponse.error(Http500)
|
return RestApiResponse.error(Http500)
|
||||||
|
|
||||||
|
router.api(
|
||||||
|
MethodGet,
|
||||||
|
"/api/codex/v1/sales/slots/{slotId}") do (slotId: SlotId) -> RestApiResponse:
|
||||||
|
## Returns active slot with id {slotId} for the host. Returns 404 if the
|
||||||
|
## slot is not active for the host.
|
||||||
|
|
||||||
|
without contracts =? node.contracts.host:
|
||||||
|
return RestApiResponse.error(Http503, "Sales unavailable")
|
||||||
|
|
||||||
|
without slotId =? slotId.tryGet.catch, error:
|
||||||
|
return RestApiResponse.error(Http400, error.msg)
|
||||||
|
|
||||||
|
without agent =? await contracts.sales.activeSale(slotId):
|
||||||
|
return RestApiResponse.error(Http404, "Provider not filling slot")
|
||||||
|
|
||||||
|
let restAgent = RestSalesAgent(
|
||||||
|
state: agent.state() |? "none",
|
||||||
|
slotIndex: agent.data.slotIndex,
|
||||||
|
requestId: agent.data.requestId
|
||||||
|
)
|
||||||
|
|
||||||
|
return RestApiResponse.response(restAgent.toJson, contentType="application/json")
|
||||||
|
|
||||||
router.api(
|
router.api(
|
||||||
MethodGet,
|
MethodGet,
|
||||||
"/api/codex/v1/sales/availability") do () -> RestApiResponse:
|
"/api/codex/v1/sales/availability") do () -> RestApiResponse:
|
||||||
|
|
|
@ -84,7 +84,7 @@ proc decodeString*(_: type array[32, byte],
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
err e.msg.cstring
|
err e.msg.cstring
|
||||||
|
|
||||||
proc decodeString*[T: PurchaseId | RequestId | Nonce](_: type T,
|
proc decodeString*[T: PurchaseId | RequestId | Nonce | SlotId](_: type T,
|
||||||
value: string): Result[T, cstring] =
|
value: string): Result[T, cstring] =
|
||||||
array[32, byte].decodeString(value).map(id => T(id))
|
array[32, byte].decodeString(value).map(id => T(id))
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
import pkg/questionable/results
|
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import pkg/libp2p
|
import pkg/libp2p
|
||||||
import pkg/codexdht/discv5/node as dn
|
import pkg/codexdht/discv5/node as dn
|
||||||
|
@ -7,7 +6,6 @@ import pkg/codexdht/discv5/routing_table as rt
|
||||||
import ../sales
|
import ../sales
|
||||||
import ../purchasing
|
import ../purchasing
|
||||||
import ../utils/json
|
import ../utils/json
|
||||||
import ../units
|
|
||||||
import ../manifest
|
import ../manifest
|
||||||
|
|
||||||
export json
|
export json
|
||||||
|
@ -34,6 +32,11 @@ type
|
||||||
minPrice* {.serialize.}: UInt256
|
minPrice* {.serialize.}: UInt256
|
||||||
maxCollateral* {.serialize.}: UInt256
|
maxCollateral* {.serialize.}: UInt256
|
||||||
|
|
||||||
|
RestSalesAgent* = object
|
||||||
|
state* {.serialize.}: string
|
||||||
|
requestId* {.serialize.}: RequestId
|
||||||
|
slotIndex* {.serialize.}: UInt256
|
||||||
|
|
||||||
RestContent* = object
|
RestContent* = object
|
||||||
cid* {.serialize.}: Cid
|
cid* {.serialize.}: Cid
|
||||||
manifest* {.serialize.}: Manifest
|
manifest* {.serialize.}: Manifest
|
||||||
|
|
|
@ -39,6 +39,7 @@ import ./utils/trackedfutures
|
||||||
|
|
||||||
export stint
|
export stint
|
||||||
export reservations
|
export reservations
|
||||||
|
export salesagent
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "sales marketplace"
|
topics = "sales marketplace"
|
||||||
|
@ -208,6 +209,14 @@ proc mySlots*(sales: Sales): Future[seq[Slot]] {.async.} =
|
||||||
|
|
||||||
return slots
|
return slots
|
||||||
|
|
||||||
|
proc activeSale*(sales: Sales, slotId: SlotId): Future[?SalesAgent] {.async.} =
|
||||||
|
let market = sales.context.market
|
||||||
|
for agent in sales.agents:
|
||||||
|
if slotId(agent.data.requestId, agent.data.slotIndex) == slotId:
|
||||||
|
return some agent
|
||||||
|
|
||||||
|
return none SalesAgent
|
||||||
|
|
||||||
proc load*(sales: Sales) {.async.} =
|
proc load*(sales: Sales) {.async.} =
|
||||||
let activeSlots = await sales.mySlots()
|
let activeSlots = await sales.mySlots()
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ push: {.upraises: [].}
|
||||||
|
|
||||||
import std/typetraits
|
import std/typetraits
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/chronicles
|
import pkg/chronicles except toJson
|
||||||
import pkg/datastore
|
import pkg/datastore
|
||||||
import pkg/nimcrypto
|
import pkg/nimcrypto
|
||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
|
@ -40,7 +40,7 @@ import ../contracts/requests
|
||||||
import ../utils/json
|
import ../utils/json
|
||||||
|
|
||||||
export requests
|
export requests
|
||||||
export chronicles
|
export chronicles except toJson
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "sales reservations"
|
topics = "sales reservations"
|
||||||
|
|
|
@ -59,6 +59,11 @@ proc retrieveRequestState*(agent: SalesAgent): Future[?RequestState] {.async.} =
|
||||||
let market = agent.context.market
|
let market = agent.context.market
|
||||||
return await market.requestState(data.requestId)
|
return await market.requestState(data.requestId)
|
||||||
|
|
||||||
|
func state*(agent: SalesAgent): ?string =
|
||||||
|
proc description(state: State): string =
|
||||||
|
$state
|
||||||
|
agent.query(description)
|
||||||
|
|
||||||
proc subscribeCancellation(agent: SalesAgent) {.async.} =
|
proc subscribeCancellation(agent: SalesAgent) {.async.} =
|
||||||
let data = agent.data
|
let data = agent.data
|
||||||
let clock = agent.context.clock
|
let clock = agent.context.clock
|
||||||
|
|
|
@ -34,7 +34,7 @@ proc transition(_: type Event, previous, next: State): Event =
|
||||||
return some next
|
return some next
|
||||||
|
|
||||||
proc query*[T](machine: Machine, query: Query[T]): ?T =
|
proc query*[T](machine: Machine, query: Query[T]): ?T =
|
||||||
if machine.state == nil:
|
if machine.state.isNil:
|
||||||
none T
|
none T
|
||||||
else:
|
else:
|
||||||
some query(machine.state)
|
some query(machine.state)
|
||||||
|
|
|
@ -18,7 +18,7 @@ import pkg/chronicles
|
||||||
import stew/io2
|
import stew/io2
|
||||||
|
|
||||||
export io2
|
export io2
|
||||||
export chronicles
|
export chronicles except toJson
|
||||||
|
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
import stew/[windows/acl]
|
import stew/[windows/acl]
|
||||||
|
|
|
@ -303,8 +303,7 @@ func `%`*[T: distinct](id: T): JsonNode =
|
||||||
type baseType = T.distinctBase
|
type baseType = T.distinctBase
|
||||||
% baseType(id)
|
% baseType(id)
|
||||||
|
|
||||||
func toJson*(obj: object): string = $(%obj)
|
func toJson*[T](item: T): string = $(%item)
|
||||||
func toJson*(obj: ref object): string = $(%obj)
|
|
||||||
|
|
||||||
proc toJsnImpl(x: NimNode): NimNode =
|
proc toJsnImpl(x: NimNode): NimNode =
|
||||||
case x.kind
|
case x.kind
|
||||||
|
|
41
openapi.yaml
41
openapi.yaml
|
@ -25,6 +25,11 @@ components:
|
||||||
description: Content Identifier as specified at https://github.com/multiformats/cid
|
description: Content Identifier as specified at https://github.com/multiformats/cid
|
||||||
example: QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N
|
example: QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N
|
||||||
|
|
||||||
|
SlotId:
|
||||||
|
type: string
|
||||||
|
description: Keccak hash of the abi encoded tuple (RequestId, slot index)
|
||||||
|
example: 268a781e0db3f7cf36b18e5f4fdb7f586ec9edd08e5500b17c0e518a769f114a
|
||||||
|
|
||||||
LogLevel:
|
LogLevel:
|
||||||
type: string
|
type: string
|
||||||
description: "One of the log levels: TRACE, DEBUG, INFO, NOTICE, WARN, ERROR or FATAL"
|
description: "One of the log levels: TRACE, DEBUG, INFO, NOTICE, WARN, ERROR or FATAL"
|
||||||
|
@ -120,8 +125,7 @@ components:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: string
|
$ref: "#/components/schemas/SlotId"
|
||||||
description: Slot ID
|
|
||||||
request:
|
request:
|
||||||
$ref: "#/components/schemas/StorageRequest"
|
$ref: "#/components/schemas/StorageRequest"
|
||||||
slotIndex:
|
slotIndex:
|
||||||
|
@ -402,6 +406,35 @@ paths:
|
||||||
"503":
|
"503":
|
||||||
description: Sales are unavailable
|
description: Sales are unavailable
|
||||||
|
|
||||||
|
"/sales/slots/{slotId}":
|
||||||
|
get:
|
||||||
|
summary: "Returns active slot with id {slotId} for the host"
|
||||||
|
tags: [ Marketplace ]
|
||||||
|
operationId: getActiveSlotById
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: slotId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Cid"
|
||||||
|
description: File to be downloaded.
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Retrieved active slot
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Slot"
|
||||||
|
|
||||||
|
"400":
|
||||||
|
description: Invalid or missing SlotId
|
||||||
|
|
||||||
|
"404":
|
||||||
|
description: Host is not in an active sale for the slot
|
||||||
|
|
||||||
|
"503":
|
||||||
|
description: Sales are unavailable
|
||||||
|
|
||||||
"/sales/availability":
|
"/sales/availability":
|
||||||
get:
|
get:
|
||||||
summary: "Returns storage that is for sale"
|
summary: "Returns storage that is for sale"
|
||||||
|
@ -504,9 +537,7 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: "#/components/schemas/Purchase"
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/Purchase"
|
|
||||||
"400":
|
"400":
|
||||||
description: Invalid or missing Purchase ID
|
description: Invalid or missing Purchase ID
|
||||||
"404":
|
"404":
|
||||||
|
|
|
@ -3,7 +3,7 @@ import std/options
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import std/strutils
|
import std/strutils
|
||||||
import std/unittest
|
import std/unittest
|
||||||
import pkg/chronicles
|
import pkg/chronicles except toJson
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import pkg/stint
|
import pkg/stint
|
||||||
import pkg/codex/contracts/requests
|
import pkg/codex/contracts/requests
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import std/times
|
import std/times
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import codex/contracts/clock
|
import codex/contracts/clock
|
||||||
|
import codex/utils/json
|
||||||
import ../ethertest
|
import ../ethertest
|
||||||
|
|
||||||
ethersuite "On-Chain Clock":
|
ethersuite "On-Chain Clock":
|
||||||
|
|
|
@ -26,4 +26,4 @@ template ethersuite*(name, body) =
|
||||||
body
|
body
|
||||||
|
|
||||||
export asynctest
|
export asynctest
|
||||||
export ethers
|
export ethers except `%`
|
||||||
|
|
|
@ -11,6 +11,8 @@ import pkg/codex/purchasing
|
||||||
import pkg/codex/errors
|
import pkg/codex/errors
|
||||||
import pkg/codex/sales/reservations
|
import pkg/codex/sales/reservations
|
||||||
|
|
||||||
|
export purchasing
|
||||||
|
|
||||||
type CodexClient* = ref object
|
type CodexClient* = ref object
|
||||||
http: HttpClient
|
http: HttpClient
|
||||||
baseurl: string
|
baseurl: string
|
||||||
|
@ -106,6 +108,18 @@ proc getPurchase*(client: CodexClient, purchaseId: PurchaseId): ?!RestPurchase =
|
||||||
let json = ? parseJson(body).catch
|
let json = ? parseJson(body).catch
|
||||||
RestPurchase.fromJson(json)
|
RestPurchase.fromJson(json)
|
||||||
|
|
||||||
|
proc getSalesAgent*(client: CodexClient, slotId: SlotId): ?!RestSalesAgent =
|
||||||
|
let url = client.baseurl & "/sales/slots/" & slotId.toHex
|
||||||
|
echo "getting sales agent for id, ", slotId.toHex
|
||||||
|
try:
|
||||||
|
let body = client.http.getContent(url)
|
||||||
|
echo "get sales agent body: ", body
|
||||||
|
let json = ? parseJson(body).catch
|
||||||
|
return RestSalesAgent.fromJson(json)
|
||||||
|
except CatchableError as e:
|
||||||
|
echo "[client.getSalesAgent] error getting agent: ", e.msg
|
||||||
|
return failure e.msg
|
||||||
|
|
||||||
proc getSlots*(client: CodexClient): ?!seq[Slot] =
|
proc getSlots*(client: CodexClient): ?!seq[Slot] =
|
||||||
let url = client.baseurl & "/sales/slots"
|
let url = client.baseurl & "/sales/slots"
|
||||||
let body = client.http.getContent(url)
|
let body = client.http.getContent(url)
|
||||||
|
@ -126,7 +140,7 @@ proc postAvailability*(
|
||||||
"maxCollateral": maxCollateral,
|
"maxCollateral": maxCollateral,
|
||||||
}
|
}
|
||||||
let response = client.http.post(url, $json)
|
let response = client.http.post(url, $json)
|
||||||
assert response.status == "200 OK"
|
doAssert response.status == "200 OK", "expected 200 OK, got " & response.status & ", body: " & response.body
|
||||||
Availability.fromJson(response.body.parseJson)
|
Availability.fromJson(response.body.parseJson)
|
||||||
|
|
||||||
proc getAvailabilities*(client: CodexClient): ?!seq[Availability] =
|
proc getAvailabilities*(client: CodexClient): ?!seq[Availability] =
|
||||||
|
@ -141,3 +155,12 @@ proc close*(client: CodexClient) =
|
||||||
proc restart*(client: CodexClient) =
|
proc restart*(client: CodexClient) =
|
||||||
client.http.close()
|
client.http.close()
|
||||||
client.http = newHttpClient()
|
client.http = newHttpClient()
|
||||||
|
|
||||||
|
proc purchaseStateIs*(client: CodexClient, id: PurchaseId, state: string): bool =
|
||||||
|
client.getPurchase(id).option.?state == some state
|
||||||
|
|
||||||
|
proc saleStateIs*(client: CodexClient, id: SlotId, state: string): bool =
|
||||||
|
client.getSalesAgent(id).option.?state == some state
|
||||||
|
|
||||||
|
proc requestId*(client: CodexClient, id: PurchaseId): ?RequestId =
|
||||||
|
return client.getPurchase(id).option.?requestId
|
||||||
|
|
Loading…
Reference in New Issue