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:
Stephen Compall 2020-01-17 14:34:27 -05:00 committed by GitHub
parent fa8a92f772
commit 0520fdfa84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 51 additions and 33 deletions

View File

@ -180,8 +180,8 @@ application/json body, formatted according to the :doc:`search-query-language`:
.. code-block:: json
{
"%templates": ["Iou:Iou"],
"amount": 999.99
"templateIds": ["Iou:Iou"],
"query": {"amount": 999.99}
}
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
:doc:`search-query-language`::
{"%templates": ["Iou:Iou"]}
{"templateIds": ["Iou:Iou"]}
output a series of JSON documents, each ``argument`` formatted according
to :doc:`lf-value-specification`::

View File

@ -6,8 +6,8 @@
The body of ``POST /contracts/search`` looks like so::
{"%templates": [...template IDs...],
...other query elements...}
{"templateIds": [...template IDs...],
"query": {...query elements...}}
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
(e.g. template IDs). For example::
{"%templates": [{"moduleName": "Foo", "entityName": "A"},
{"moduleName": "Foo", "entityName": "B"},
{"moduleName": "Foo", "entityName": "C"}],
"foo": "bar"}
{"templateIds": [{"moduleName": "Foo", "entityName": "A"},
{"moduleName": "Foo", "entityName": "B"},
{"moduleName": "Foo", "entityName": "C"}],
"query": {"foo": "bar"}}
will treat ``"foo"`` as a field equality query for A and B, and
(supposing templates' associated data types were permitted to be

View File

@ -127,7 +127,7 @@ class Ledger {
* for a description of the query language.
*/
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);
return jtv.Result.withException(jtv.array(decodeCreateEvent(template)).run(json));
}

View File

@ -196,17 +196,28 @@ object JsonProtocol extends DefaultJsonProtocol {
implicit val ArchivedContractFormat: RootJsonFormat[domain.ArchivedContract] =
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] = {
case JsObject(fields) =>
val templates = (fields get templatesKey)
.map(_.convertTo[Set[domain.TemplateId.OptionalPkg]])
.filter(_.nonEmpty)
.getOrElse(
deserializationError("/contracts/search requires at least one item in '%templates'"))
domain.GetActiveContractsRequest(templateIds = templates, query = fields - templatesKey)
case _ => deserializationError("/contracts/search must receive an object")
case class GACR(
templateIds: Set[domain.TemplateId.OptionalPkg],
query: Option[Map[String, JsValue]])
val validKeys = Set("templateIds", "query")
implicit val primitive: JsonReader[GACR] = jsonFormat2(GACR.apply)
jsv =>
{
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(

View File

@ -143,7 +143,7 @@ abstract class AbstractHttpServiceIntegrationTest
"contracts/search POST with empty query" in withHttpService { (uri, encoder, _) =>
searchWithQuery(
searchDataSet,
jsObject("""{"%templates": ["Iou:Iou"]}"""),
jsObject("""{"templateIds": ["Iou:Iou"]}"""),
uri,
encoder
).map { acl: List[domain.ActiveContract[JsValue]] =>
@ -154,7 +154,7 @@ abstract class AbstractHttpServiceIntegrationTest
"contracts/search with query, one field" in withHttpService { (uri, encoder, _) =>
searchWithQuery(
searchDataSet,
jsObject("""{"%templates": ["Iou:Iou"], "currency": "EUR"}"""),
jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""),
uri,
encoder
).map { acl: List[domain.ActiveContract[JsValue]] =>
@ -165,7 +165,8 @@ abstract class AbstractHttpServiceIntegrationTest
"contracts/search returns unknown Template IDs as warnings" in withHttpService { (uri, _, _) =>
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)
.map {
@ -186,8 +187,10 @@ abstract class AbstractHttpServiceIntegrationTest
rs: List[(StatusCode, JsValue)] =>
rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK)
val queryAmountAsString = jsObject("""{"%templates": ["Iou:Iou"], "amount": "111.11"}""")
val queryAmountAsNumber = jsObject("""{"%templates": ["Iou:Iou"], "amount": 111.11}""")
def queryAmountAs(s: String) =
jsObject(s"""{"templateIds": ["Iou:Iou"], "query": {"amount": $s}}""")
val queryAmountAsString = queryAmountAs("\"111.11\"")
val queryAmountAsNumber = queryAmountAs("111.11")
List(
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, _) =>
searchWithQuery(
searchDataSet,
jsObject("""{"%templates": ["Iou:Iou"], "currency": "EUR", "amount": "111.11"}"""),
jsObject(
"""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR", "amount": "111.11"}}"""),
uri,
encoder).map { acl: List[domain.ActiveContract[JsValue]] =>
acl.size shouldBe 1
@ -224,7 +228,8 @@ abstract class AbstractHttpServiceIntegrationTest
"contracts/search with query, no results" in withHttpService { (uri, encoder, _) =>
searchWithQuery(
searchDataSet,
jsObject("""{"%templates": ["Iou:Iou"], "currency": "RUB", "amount": "666.66"}"""),
jsObject(
"""{"templateIds": ["Iou:Iou"], "query": {"currency": "RUB", "amount": "666.66"}}"""),
uri,
encoder
).map { acl: List[domain.ActiveContract[JsValue]] =>
@ -653,9 +658,11 @@ abstract class AbstractHttpServiceIntegrationTest
val contractId: ContractId = getContractId(getResult(output))
val query = jsObject(s"""{
"%templates": ["$packageId:Account:Account"],
"number" : "abc123",
"status" : {"tag": "Enabled", "value": "${nowStr: String}"}
"templateIds": ["$packageId:Account:Account"],
"query": {
"number" : "abc123",
"status" : {"tag": "Enabled", "value": "${nowStr: String}"}
}
}""")
postJsonRequest(uri.withPath(Uri.Path("/contracts/search")), query).map {

View File

@ -35,7 +35,7 @@ class HttpServiceWithPostgresIntTest
"contracts/search persists all active contracts" in withHttpService { (uri, encoder, _) =>
searchWithQuery(
searchDataSet,
jsObject("""{"%templates": ["Iou:Iou"], "currency": "EUR"}"""),
jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""),
uri,
encoder
).flatMap { searchResult: List[domain.ActiveContract[JsValue]] =>

View File

@ -83,7 +83,7 @@ class WebsocketServiceIntegrationTest
subprotocol = validSubprotocol))
clientMsg <- Source
.single(TextMessage("""{"%templates": ["Iou:Iou"]}"""))
.single(TextMessage("""{"templateIds": ["Iou:Iou"]}"""))
.via(webSocketFlow)
.runWith(collectResultsAsRawString)
} yield
@ -148,7 +148,7 @@ class WebsocketServiceIntegrationTest
subprotocol = validSubprotocol))
val query =
TextMessage.Strict("""{"%templates": ["Iou:Iou"]}""")
TextMessage.Strict("""{"templateIds": ["Iou:Iou"]}""")
val parseResp: Flow[Message, JsValue, NotUsed] =
Flow[Message]