mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 09:17:43 +03:00
in query argument, rename %templates to templateIds, and nest query under 'query' field (#4082)
* in query argument, rename %templates to templateIds, and nest query under 'query' field CHANGELOG_BEGIN - [JSON API - Experimental] In 'search' endpoint arguments, %templates is now templateIds. Additionally, all contract query fields must occur under 'query'. See `issue #3450 <https://github.com/digital-asset/daml/issues/3450>`__. CHANGELOG_END * fix other old query format usages
This commit is contained in:
parent
fa8a92f772
commit
0520fdfa84
@ -180,8 +180,8 @@ application/json body, formatted according to the :doc:`search-query-language`:
|
|||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"%templates": ["Iou:Iou"],
|
"templateIds": ["Iou:Iou"],
|
||||||
"amount": 999.99
|
"query": {"amount": 999.99}
|
||||||
}
|
}
|
||||||
|
|
||||||
Empty Response
|
Empty Response
|
||||||
@ -265,7 +265,7 @@ Two subprotocols must be passed, as described in `Choosing a party
|
|||||||
application/json body must be sent first, formatted according to the
|
application/json body must be sent first, formatted according to the
|
||||||
:doc:`search-query-language`::
|
:doc:`search-query-language`::
|
||||||
|
|
||||||
{"%templates": ["Iou:Iou"]}
|
{"templateIds": ["Iou:Iou"]}
|
||||||
|
|
||||||
output a series of JSON documents, each ``argument`` formatted according
|
output a series of JSON documents, each ``argument`` formatted according
|
||||||
to :doc:`lf-value-specification`::
|
to :doc:`lf-value-specification`::
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
The body of ``POST /contracts/search`` looks like so::
|
The body of ``POST /contracts/search`` looks like so::
|
||||||
|
|
||||||
{"%templates": [...template IDs...],
|
{"templateIds": [...template IDs...],
|
||||||
...other query elements...}
|
"query": {...query elements...}}
|
||||||
|
|
||||||
The elements of that query are defined here.
|
The elements of that query are defined here.
|
||||||
|
|
||||||
@ -108,10 +108,10 @@ For these reasons, as with LF value input via JSON, queries written in
|
|||||||
JSON are also always interpreted with respect to some specified LF types
|
JSON are also always interpreted with respect to some specified LF types
|
||||||
(e.g. template IDs). For example::
|
(e.g. template IDs). For example::
|
||||||
|
|
||||||
{"%templates": [{"moduleName": "Foo", "entityName": "A"},
|
{"templateIds": [{"moduleName": "Foo", "entityName": "A"},
|
||||||
{"moduleName": "Foo", "entityName": "B"},
|
{"moduleName": "Foo", "entityName": "B"},
|
||||||
{"moduleName": "Foo", "entityName": "C"}],
|
{"moduleName": "Foo", "entityName": "C"}],
|
||||||
"foo": "bar"}
|
"query": {"foo": "bar"}}
|
||||||
|
|
||||||
will treat ``"foo"`` as a field equality query for A and B, and
|
will treat ``"foo"`` as a field equality query for A and B, and
|
||||||
(supposing templates' associated data types were permitted to be
|
(supposing templates' associated data types were permitted to be
|
||||||
|
@ -127,7 +127,7 @@ class Ledger {
|
|||||||
* for a description of the query language.
|
* for a description of the query language.
|
||||||
*/
|
*/
|
||||||
async query<T extends object, K>(template: Template<T, K>, query: Query<T>): Promise<CreateEvent<T, K>[]> {
|
async query<T extends object, K>(template: Template<T, K>, query: Query<T>): Promise<CreateEvent<T, K>[]> {
|
||||||
const payload = {"%templates": [template.templateId], ...query};
|
const payload = {templateIds: [template.templateId], query};
|
||||||
const json = await this.submit('contracts/search', payload);
|
const json = await this.submit('contracts/search', payload);
|
||||||
return jtv.Result.withException(jtv.array(decodeCreateEvent(template)).run(json));
|
return jtv.Result.withException(jtv.array(decodeCreateEvent(template)).run(json));
|
||||||
}
|
}
|
||||||
|
@ -196,17 +196,28 @@ object JsonProtocol extends DefaultJsonProtocol {
|
|||||||
implicit val ArchivedContractFormat: RootJsonFormat[domain.ArchivedContract] =
|
implicit val ArchivedContractFormat: RootJsonFormat[domain.ArchivedContract] =
|
||||||
jsonFormat2(domain.ArchivedContract.apply)
|
jsonFormat2(domain.ArchivedContract.apply)
|
||||||
|
|
||||||
private val templatesKey = "%templates"
|
/** Derived from autogenerated with 3 extra features:
|
||||||
|
* 1. template IDs are required
|
||||||
|
* 2. query key may be absent
|
||||||
|
* 3. special error if you appear to have queried outside 'query'
|
||||||
|
*/
|
||||||
implicit val GetActiveContractsRequestFormat: RootJsonReader[domain.GetActiveContractsRequest] = {
|
implicit val GetActiveContractsRequestFormat: RootJsonReader[domain.GetActiveContractsRequest] = {
|
||||||
case JsObject(fields) =>
|
case class GACR(
|
||||||
val templates = (fields get templatesKey)
|
templateIds: Set[domain.TemplateId.OptionalPkg],
|
||||||
.map(_.convertTo[Set[domain.TemplateId.OptionalPkg]])
|
query: Option[Map[String, JsValue]])
|
||||||
.filter(_.nonEmpty)
|
val validKeys = Set("templateIds", "query")
|
||||||
.getOrElse(
|
implicit val primitive: JsonReader[GACR] = jsonFormat2(GACR.apply)
|
||||||
deserializationError("/contracts/search requires at least one item in '%templates'"))
|
jsv =>
|
||||||
domain.GetActiveContractsRequest(templateIds = templates, query = fields - templatesKey)
|
{
|
||||||
case _ => deserializationError("/contracts/search must receive an object")
|
val GACR(tids, q) = jsv.convertTo[GACR]
|
||||||
|
val extras = jsv.asJsObject.fields.keySet diff validKeys
|
||||||
|
if (tids.isEmpty)
|
||||||
|
deserializationError("search requires at least one item in 'templateIds'")
|
||||||
|
else if (extras.nonEmpty)
|
||||||
|
deserializationError(
|
||||||
|
s"unsupported query fields ${extras}; likely should be within 'query' subobject")
|
||||||
|
domain.GetActiveContractsRequest(tids, q getOrElse Map.empty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit val CommandMetaFormat: RootJsonFormat[domain.CommandMeta] = jsonFormat3(
|
implicit val CommandMetaFormat: RootJsonFormat[domain.CommandMeta] = jsonFormat3(
|
||||||
|
@ -143,7 +143,7 @@ abstract class AbstractHttpServiceIntegrationTest
|
|||||||
"contracts/search POST with empty query" in withHttpService { (uri, encoder, _) =>
|
"contracts/search POST with empty query" in withHttpService { (uri, encoder, _) =>
|
||||||
searchWithQuery(
|
searchWithQuery(
|
||||||
searchDataSet,
|
searchDataSet,
|
||||||
jsObject("""{"%templates": ["Iou:Iou"]}"""),
|
jsObject("""{"templateIds": ["Iou:Iou"]}"""),
|
||||||
uri,
|
uri,
|
||||||
encoder
|
encoder
|
||||||
).map { acl: List[domain.ActiveContract[JsValue]] =>
|
).map { acl: List[domain.ActiveContract[JsValue]] =>
|
||||||
@ -154,7 +154,7 @@ abstract class AbstractHttpServiceIntegrationTest
|
|||||||
"contracts/search with query, one field" in withHttpService { (uri, encoder, _) =>
|
"contracts/search with query, one field" in withHttpService { (uri, encoder, _) =>
|
||||||
searchWithQuery(
|
searchWithQuery(
|
||||||
searchDataSet,
|
searchDataSet,
|
||||||
jsObject("""{"%templates": ["Iou:Iou"], "currency": "EUR"}"""),
|
jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""),
|
||||||
uri,
|
uri,
|
||||||
encoder
|
encoder
|
||||||
).map { acl: List[domain.ActiveContract[JsValue]] =>
|
).map { acl: List[domain.ActiveContract[JsValue]] =>
|
||||||
@ -165,7 +165,8 @@ abstract class AbstractHttpServiceIntegrationTest
|
|||||||
|
|
||||||
"contracts/search returns unknown Template IDs as warnings" in withHttpService { (uri, _, _) =>
|
"contracts/search returns unknown Template IDs as warnings" in withHttpService { (uri, _, _) =>
|
||||||
val query =
|
val query =
|
||||||
jsObject("""{"%templates": ["Iou:Iou", "UnknownModule:UnknownEntity"], "currency": "EUR"}""")
|
jsObject(
|
||||||
|
"""{"templateIds": ["Iou:Iou", "UnknownModule:UnknownEntity"], "query": {"currency": "EUR"}}""")
|
||||||
|
|
||||||
postJsonRequest(uri.withPath(Uri.Path("/contracts/search")), query)
|
postJsonRequest(uri.withPath(Uri.Path("/contracts/search")), query)
|
||||||
.map {
|
.map {
|
||||||
@ -186,8 +187,10 @@ abstract class AbstractHttpServiceIntegrationTest
|
|||||||
rs: List[(StatusCode, JsValue)] =>
|
rs: List[(StatusCode, JsValue)] =>
|
||||||
rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK)
|
rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK)
|
||||||
|
|
||||||
val queryAmountAsString = jsObject("""{"%templates": ["Iou:Iou"], "amount": "111.11"}""")
|
def queryAmountAs(s: String) =
|
||||||
val queryAmountAsNumber = jsObject("""{"%templates": ["Iou:Iou"], "amount": 111.11}""")
|
jsObject(s"""{"templateIds": ["Iou:Iou"], "query": {"amount": $s}}""")
|
||||||
|
val queryAmountAsString = queryAmountAs("\"111.11\"")
|
||||||
|
val queryAmountAsNumber = queryAmountAs("111.11")
|
||||||
|
|
||||||
List(
|
List(
|
||||||
postJsonRequest(uri.withPath(Uri.Path("/contracts/search")), queryAmountAsString),
|
postJsonRequest(uri.withPath(Uri.Path("/contracts/search")), queryAmountAsString),
|
||||||
@ -212,7 +215,8 @@ abstract class AbstractHttpServiceIntegrationTest
|
|||||||
"contracts/search with query, two fields" in withHttpService { (uri, encoder, _) =>
|
"contracts/search with query, two fields" in withHttpService { (uri, encoder, _) =>
|
||||||
searchWithQuery(
|
searchWithQuery(
|
||||||
searchDataSet,
|
searchDataSet,
|
||||||
jsObject("""{"%templates": ["Iou:Iou"], "currency": "EUR", "amount": "111.11"}"""),
|
jsObject(
|
||||||
|
"""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR", "amount": "111.11"}}"""),
|
||||||
uri,
|
uri,
|
||||||
encoder).map { acl: List[domain.ActiveContract[JsValue]] =>
|
encoder).map { acl: List[domain.ActiveContract[JsValue]] =>
|
||||||
acl.size shouldBe 1
|
acl.size shouldBe 1
|
||||||
@ -224,7 +228,8 @@ abstract class AbstractHttpServiceIntegrationTest
|
|||||||
"contracts/search with query, no results" in withHttpService { (uri, encoder, _) =>
|
"contracts/search with query, no results" in withHttpService { (uri, encoder, _) =>
|
||||||
searchWithQuery(
|
searchWithQuery(
|
||||||
searchDataSet,
|
searchDataSet,
|
||||||
jsObject("""{"%templates": ["Iou:Iou"], "currency": "RUB", "amount": "666.66"}"""),
|
jsObject(
|
||||||
|
"""{"templateIds": ["Iou:Iou"], "query": {"currency": "RUB", "amount": "666.66"}}"""),
|
||||||
uri,
|
uri,
|
||||||
encoder
|
encoder
|
||||||
).map { acl: List[domain.ActiveContract[JsValue]] =>
|
).map { acl: List[domain.ActiveContract[JsValue]] =>
|
||||||
@ -653,9 +658,11 @@ abstract class AbstractHttpServiceIntegrationTest
|
|||||||
val contractId: ContractId = getContractId(getResult(output))
|
val contractId: ContractId = getContractId(getResult(output))
|
||||||
|
|
||||||
val query = jsObject(s"""{
|
val query = jsObject(s"""{
|
||||||
"%templates": ["$packageId:Account:Account"],
|
"templateIds": ["$packageId:Account:Account"],
|
||||||
|
"query": {
|
||||||
"number" : "abc123",
|
"number" : "abc123",
|
||||||
"status" : {"tag": "Enabled", "value": "${nowStr: String}"}
|
"status" : {"tag": "Enabled", "value": "${nowStr: String}"}
|
||||||
|
}
|
||||||
}""")
|
}""")
|
||||||
|
|
||||||
postJsonRequest(uri.withPath(Uri.Path("/contracts/search")), query).map {
|
postJsonRequest(uri.withPath(Uri.Path("/contracts/search")), query).map {
|
||||||
|
@ -35,7 +35,7 @@ class HttpServiceWithPostgresIntTest
|
|||||||
"contracts/search persists all active contracts" in withHttpService { (uri, encoder, _) =>
|
"contracts/search persists all active contracts" in withHttpService { (uri, encoder, _) =>
|
||||||
searchWithQuery(
|
searchWithQuery(
|
||||||
searchDataSet,
|
searchDataSet,
|
||||||
jsObject("""{"%templates": ["Iou:Iou"], "currency": "EUR"}"""),
|
jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""),
|
||||||
uri,
|
uri,
|
||||||
encoder
|
encoder
|
||||||
).flatMap { searchResult: List[domain.ActiveContract[JsValue]] =>
|
).flatMap { searchResult: List[domain.ActiveContract[JsValue]] =>
|
||||||
|
@ -83,7 +83,7 @@ class WebsocketServiceIntegrationTest
|
|||||||
subprotocol = validSubprotocol))
|
subprotocol = validSubprotocol))
|
||||||
|
|
||||||
clientMsg <- Source
|
clientMsg <- Source
|
||||||
.single(TextMessage("""{"%templates": ["Iou:Iou"]}"""))
|
.single(TextMessage("""{"templateIds": ["Iou:Iou"]}"""))
|
||||||
.via(webSocketFlow)
|
.via(webSocketFlow)
|
||||||
.runWith(collectResultsAsRawString)
|
.runWith(collectResultsAsRawString)
|
||||||
} yield
|
} yield
|
||||||
@ -148,7 +148,7 @@ class WebsocketServiceIntegrationTest
|
|||||||
subprotocol = validSubprotocol))
|
subprotocol = validSubprotocol))
|
||||||
|
|
||||||
val query =
|
val query =
|
||||||
TextMessage.Strict("""{"%templates": ["Iou:Iou"]}""")
|
TextMessage.Strict("""{"templateIds": ["Iou:Iou"]}""")
|
||||||
|
|
||||||
val parseResp: Flow[Message, JsValue, NotUsed] =
|
val parseResp: Flow[Message, JsValue, NotUsed] =
|
||||||
Flow[Message]
|
Flow[Message]
|
||||||
|
Loading…
Reference in New Issue
Block a user