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
|
||||
|
||||
export purchasing
|
||||
export chronicles
|
||||
export chronicles except toJson
|
||||
|
||||
type
|
||||
ClientInteractions* = ref object of ContractInteractions
|
||||
|
|
|
@ -5,7 +5,7 @@ import ../../sales
|
|||
import ./interactions
|
||||
|
||||
export sales
|
||||
export chronicles
|
||||
export chronicles except toJson
|
||||
|
||||
type
|
||||
HostInteractions* = ref object of ContractInteractions
|
||||
|
|
|
@ -8,8 +8,8 @@ import ./requests
|
|||
import ./config
|
||||
|
||||
export stint
|
||||
export ethers
|
||||
export erc20
|
||||
export ethers except `%`, `%*`, toJson
|
||||
export erc20 except `%`, `%*`, toJson
|
||||
export config
|
||||
export requests
|
||||
|
||||
|
|
|
@ -75,6 +75,10 @@ proc fromHex*[T: distinct](_: type T, hex: string): T =
|
|||
type baseType = T.distinctBase
|
||||
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 =
|
||||
StorageRequest(
|
||||
client: tupl[0],
|
||||
|
|
|
@ -16,11 +16,10 @@ import std/sequtils
|
|||
|
||||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
import pkg/chronicles
|
||||
import pkg/chronicles except toJson
|
||||
import pkg/chronos
|
||||
import pkg/presto
|
||||
import pkg/libp2p
|
||||
import pkg/metrics
|
||||
import pkg/presto except toJson
|
||||
import pkg/metrics except toJson
|
||||
import pkg/stew/base10
|
||||
import pkg/stew/byteutils
|
||||
import pkg/confutils
|
||||
|
@ -32,8 +31,10 @@ import pkg/codexdht/discv5/spr as spr
|
|||
import ../node
|
||||
import ../blocktype
|
||||
import ../conf
|
||||
import ../contracts except `%*`, `%` # imported from contracts/marketplace (exporting ethers)
|
||||
import ../streams
|
||||
import ../contracts
|
||||
import ../manifest
|
||||
import ../streams/asyncstreamwrapper
|
||||
import ../stores/blockstore
|
||||
|
||||
import ./coders
|
||||
import ./json
|
||||
|
@ -197,6 +198,29 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) =
|
|||
trace "Excepting processing request", exc = exc.msg
|
||||
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(
|
||||
MethodGet,
|
||||
"/api/codex/v1/sales/availability") do () -> RestApiResponse:
|
||||
|
|
|
@ -84,7 +84,7 @@ proc decodeString*(_: type array[32, byte],
|
|||
except ValueError as e:
|
||||
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] =
|
||||
array[32, byte].decodeString(value).map(id => T(id))
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
import pkg/stew/byteutils
|
||||
import pkg/libp2p
|
||||
import pkg/codexdht/discv5/node as dn
|
||||
|
@ -7,7 +6,6 @@ import pkg/codexdht/discv5/routing_table as rt
|
|||
import ../sales
|
||||
import ../purchasing
|
||||
import ../utils/json
|
||||
import ../units
|
||||
import ../manifest
|
||||
|
||||
export json
|
||||
|
@ -34,6 +32,11 @@ type
|
|||
minPrice* {.serialize.}: UInt256
|
||||
maxCollateral* {.serialize.}: UInt256
|
||||
|
||||
RestSalesAgent* = object
|
||||
state* {.serialize.}: string
|
||||
requestId* {.serialize.}: RequestId
|
||||
slotIndex* {.serialize.}: UInt256
|
||||
|
||||
RestContent* = object
|
||||
cid* {.serialize.}: Cid
|
||||
manifest* {.serialize.}: Manifest
|
||||
|
|
|
@ -39,6 +39,7 @@ import ./utils/trackedfutures
|
|||
|
||||
export stint
|
||||
export reservations
|
||||
export salesagent
|
||||
|
||||
logScope:
|
||||
topics = "sales marketplace"
|
||||
|
@ -208,6 +209,14 @@ proc mySlots*(sales: Sales): Future[seq[Slot]] {.async.} =
|
|||
|
||||
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.} =
|
||||
let activeSlots = await sales.mySlots()
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ push: {.upraises: [].}
|
|||
|
||||
import std/typetraits
|
||||
import pkg/chronos
|
||||
import pkg/chronicles
|
||||
import pkg/chronicles except toJson
|
||||
import pkg/datastore
|
||||
import pkg/nimcrypto
|
||||
import pkg/questionable
|
||||
|
@ -40,7 +40,7 @@ import ../contracts/requests
|
|||
import ../utils/json
|
||||
|
||||
export requests
|
||||
export chronicles
|
||||
export chronicles except toJson
|
||||
|
||||
logScope:
|
||||
topics = "sales reservations"
|
||||
|
|
|
@ -59,6 +59,11 @@ proc retrieveRequestState*(agent: SalesAgent): Future[?RequestState] {.async.} =
|
|||
let market = agent.context.market
|
||||
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.} =
|
||||
let data = agent.data
|
||||
let clock = agent.context.clock
|
||||
|
|
|
@ -34,7 +34,7 @@ proc transition(_: type Event, previous, next: State): Event =
|
|||
return some next
|
||||
|
||||
proc query*[T](machine: Machine, query: Query[T]): ?T =
|
||||
if machine.state == nil:
|
||||
if machine.state.isNil:
|
||||
none T
|
||||
else:
|
||||
some query(machine.state)
|
||||
|
|
|
@ -18,7 +18,7 @@ import pkg/chronicles
|
|||
import stew/io2
|
||||
|
||||
export io2
|
||||
export chronicles
|
||||
export chronicles except toJson
|
||||
|
||||
when defined(windows):
|
||||
import stew/[windows/acl]
|
||||
|
|
|
@ -303,8 +303,7 @@ func `%`*[T: distinct](id: T): JsonNode =
|
|||
type baseType = T.distinctBase
|
||||
% baseType(id)
|
||||
|
||||
func toJson*(obj: object): string = $(%obj)
|
||||
func toJson*(obj: ref object): string = $(%obj)
|
||||
func toJson*[T](item: T): string = $(%item)
|
||||
|
||||
proc toJsnImpl(x: NimNode): NimNode =
|
||||
case x.kind
|
||||
|
|
39
openapi.yaml
39
openapi.yaml
|
@ -25,6 +25,11 @@ components:
|
|||
description: Content Identifier as specified at https://github.com/multiformats/cid
|
||||
example: QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N
|
||||
|
||||
SlotId:
|
||||
type: string
|
||||
description: Keccak hash of the abi encoded tuple (RequestId, slot index)
|
||||
example: 268a781e0db3f7cf36b18e5f4fdb7f586ec9edd08e5500b17c0e518a769f114a
|
||||
|
||||
LogLevel:
|
||||
type: string
|
||||
description: "One of the log levels: TRACE, DEBUG, INFO, NOTICE, WARN, ERROR or FATAL"
|
||||
|
@ -120,8 +125,7 @@ components:
|
|||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Slot ID
|
||||
$ref: "#/components/schemas/SlotId"
|
||||
request:
|
||||
$ref: "#/components/schemas/StorageRequest"
|
||||
slotIndex:
|
||||
|
@ -402,6 +406,35 @@ paths:
|
|||
"503":
|
||||
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":
|
||||
get:
|
||||
summary: "Returns storage that is for sale"
|
||||
|
@ -504,8 +537,6 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Purchase"
|
||||
"400":
|
||||
description: Invalid or missing Purchase ID
|
||||
|
|
|
@ -3,7 +3,7 @@ import std/options
|
|||
import std/strformat
|
||||
import std/strutils
|
||||
import std/unittest
|
||||
import pkg/chronicles
|
||||
import pkg/chronicles except toJson
|
||||
import pkg/stew/byteutils
|
||||
import pkg/stint
|
||||
import pkg/codex/contracts/requests
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import std/times
|
||||
import pkg/chronos
|
||||
import codex/contracts/clock
|
||||
import codex/utils/json
|
||||
import ../ethertest
|
||||
|
||||
ethersuite "On-Chain Clock":
|
||||
|
|
|
@ -26,4 +26,4 @@ template ethersuite*(name, body) =
|
|||
body
|
||||
|
||||
export asynctest
|
||||
export ethers
|
||||
export ethers except `%`
|
||||
|
|
|
@ -11,6 +11,8 @@ import pkg/codex/purchasing
|
|||
import pkg/codex/errors
|
||||
import pkg/codex/sales/reservations
|
||||
|
||||
export purchasing
|
||||
|
||||
type CodexClient* = ref object
|
||||
http: HttpClient
|
||||
baseurl: string
|
||||
|
@ -106,6 +108,18 @@ proc getPurchase*(client: CodexClient, purchaseId: PurchaseId): ?!RestPurchase =
|
|||
let json = ? parseJson(body).catch
|
||||
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] =
|
||||
let url = client.baseurl & "/sales/slots"
|
||||
let body = client.http.getContent(url)
|
||||
|
@ -126,7 +140,7 @@ proc postAvailability*(
|
|||
"maxCollateral": maxCollateral,
|
||||
}
|
||||
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)
|
||||
|
||||
proc getAvailabilities*(client: CodexClient): ?!seq[Availability] =
|
||||
|
@ -141,3 +155,12 @@ proc close*(client: CodexClient) =
|
|||
proc restart*(client: CodexClient) =
|
||||
client.http.close()
|
||||
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