Repository: TBD54566975/ssi-service
Pull request #197 (2022-12-06 00:32:01)

[OSE-173] Filter implementation with tests
# Overview
This change adds filtering capabilities for the `GetOperations` and `ListSubmissions` endpoints. 

# Description
This implementation sticks to the following pattern:
* Parse the `filter` parameter in the request using the `einride` library. This parses into an `expression` protobuf.
* Evaluate the parsed expression by passing the variable values based off of what element was retrieved from storage.

The implementation relies on knowledge of google's CEL (see this [codelab](https://codelabs.developers.google.com/codelabs/cel-go/index.html#0) for a good intro). There are alternatives to doing this, but CEL makes it very easy to extend and to translate into multiple backends once we want to support more complex filtering queries quickly.

# How Has This Been Tested?
See unit tests added.

# Checklist

Before submitting this PR, please make sure:

- [x] I have read the CONTRIBUTING document.
- [x] My code is consistent with the rest of the project 
- [x] I have tagged the relevant reviewers and/or interested parties
- [x] I have updated the READMEs and other documentation of affected packages

## References
* https://pkg.go.dev/go.einride.tech/aip@v0.60.0
* https://codelabs.developers.google.com/codelabs/cel-go/index.html#0



Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9934430..46cf4aa 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -48,7 +48,7 @@ jobs:
       shell: bash
       run: |
         export PATH=$PATH:$(go env GOPATH)/bin
-        go install github.com/swaggo/swag/cmd/swag@v1.8.2
+        go install github.com/swaggo/swag/cmd/swag@v1.8.7
         mage spec
 
     - name: Push generated spec file
diff --git a/doc/swagger.yaml b/doc/swagger.yaml
index 096689b..f0685a4 100644
--- a/doc/swagger.yaml
+++ b/doc/swagger.yaml
@@ -710,7 +710,7 @@ definitions:
       filter:
         description: |-
           A standard filter expression conforming to https://google.aip.dev/160.
-          For example: 'status = done'.
+          For example: `done = true`.
         type: string
       parent:
         description: 'The name of the parent''s resource. For example: "/presentation/submissions".'
@@ -778,6 +778,9 @@ definitions:
   github.com_tbd54566975_ssi-service_pkg_server_router.ListSubmissionRequest:
     properties:
       filter:
+        description: |-
+          A standard filter expression conforming to https://google.aip.dev/160.
+          For example: `status = "done"`.
         type: string
     type: object
   github.com_tbd54566975_ssi-service_pkg_server_router.ListSubmissionResponse:
@@ -1259,7 +1262,7 @@ definitions:
       filter:
         description: |-
           A standard filter expression conforming to https://google.aip.dev/160.
-          For example: 'status = done'.
+          For example: `done = true`.
         type: string
       parent:
         description: 'The name of the parent''s resource. For example: "/presentation/submissions".'
@@ -1327,6 +1330,9 @@ definitions:
   pkg_server_router.ListSubmissionRequest:
     properties:
       filter:
+        description: |-
+          A standard filter expression conforming to https://google.aip.dev/160.
+          For example: `status = "done"`.
         type: string
     type: object
   pkg_server_router.ListSubmissionResponse:
@@ -1609,7 +1615,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/pkg_server_router.GetHealthCheckResponse'
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetHealthCheckResponse'
       summary: Health Check
       tags:
       - HealthCheck
@@ -1865,7 +1871,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetDIDMethodsResponse'
+            $ref: '#/definitions/pkg_server_router.GetDIDMethodsResponse'
       summary: Get DID Methods
       tags:
       - DecentralizedIdentityAPI
@@ -1886,7 +1892,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetDIDsByMethodResponse'
+            $ref: '#/definitions/pkg_server_router.GetDIDsByMethodResponse'
         "400":
           description: Bad request
           schema:
@@ -1904,7 +1910,7 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreateDIDByMethodRequest'
+          $ref: '#/definitions/pkg_server_router.CreateDIDByMethodRequest'
       - description: Method
         in: path
         name: method
@@ -1916,7 +1922,7 @@ paths:
         "201":
           description: Created
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreateDIDByMethodResponse'
+            $ref: '#/definitions/pkg_server_router.CreateDIDByMethodResponse'
         "400":
           description: Bad request
           schema:
@@ -1939,7 +1945,7 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreateDIDByMethodRequest'
+          $ref: '#/definitions/pkg_server_router.CreateDIDByMethodRequest'
       - description: Method
         in: path
         name: method
@@ -1956,7 +1962,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetDIDByMethodResponse'
+            $ref: '#/definitions/pkg_server_router.GetDIDByMethodResponse'
         "400":
           description: Bad request
           schema:
@@ -1981,7 +1987,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.ResolveDIDResponse'
+            $ref: '#/definitions/pkg_server_router.ResolveDIDResponse'
         "400":
           description: Bad request
           schema:
@@ -2000,7 +2006,7 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.StoreKeyRequest'
+          $ref: '#/definitions/pkg_server_router.StoreKeyRequest'
       produces:
       - application/json
       responses:
@@ -2034,7 +2040,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetKeyDetailsResponse'
+            $ref: '#/definitions/pkg_server_router.GetKeyDetailsResponse'
         "400":
           description: Bad request
           schema:
@@ -2362,7 +2368,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetOperationsResponse'
+            $ref: '#/definitions/pkg_server_router.GetOperationsResponse'
         "400":
           description: Bad request
           schema:
@@ -2391,7 +2397,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.Operation'
+            $ref: '#/definitions/pkg_server_router.Operation'
         "400":
           description: Bad request
           schema:
@@ -2414,14 +2420,14 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreatePresentationDefinitionRequest'
+          $ref: '#/definitions/pkg_server_router.CreatePresentationDefinitionRequest'
       produces:
       - application/json
       responses:
         "201":
           description: Created
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreatePresentationDefinitionResponse'
+            $ref: '#/definitions/pkg_server_router.CreatePresentationDefinitionResponse'
         "400":
           description: Bad request
           schema:
@@ -2478,7 +2484,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetPresentationDefinitionResponse'
+            $ref: '#/definitions/pkg_server_router.GetPresentationDefinitionResponse'
         "400":
           description: Bad request
           schema:
@@ -2503,7 +2509,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetSubmissionResponse'
+            $ref: '#/definitions/pkg_server_router.GetSubmissionResponse'
         "400":
           description: Bad request
           schema:
@@ -2524,14 +2530,14 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.ReviewSubmissionRequest'
+          $ref: '#/definitions/pkg_server_router.ReviewSubmissionRequest'
       produces:
       - application/json
       responses:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.ReviewSubmissionResponse'
+            $ref: '#/definitions/pkg_server_router.ReviewSubmissionResponse'
         "400":
           description: Bad request
           schema:
@@ -2553,14 +2559,14 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreateSubmissionRequest'
+          $ref: '#/definitions/pkg_server_router.CreateSubmissionRequest'
       produces:
       - application/json
       responses:
         "201":
           description: The type of response is Submission once the operation has finished.
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.Operation'
+            $ref: '#/definitions/pkg_server_router.Operation'
         "400":
           description: Bad request
           schema:
diff --git a/go.mod b/go.mod
index 4913713..3b4d8e8 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
 	github.com/go-playground/locales v0.14.0
 	github.com/go-playground/universal-translator v0.18.0
 	github.com/goccy/go-json v0.10.0
+	github.com/google/cel-go v0.12.5
 	github.com/google/go-cmp v0.5.9
 	github.com/google/uuid v1.3.0
 	github.com/lestrrat-go/jwx v1.2.25
@@ -21,6 +22,7 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.9.0
 	github.com/stretchr/testify v1.8.1
+	go.einride.tech/aip v0.60.0
 	go.etcd.io/bbolt v1.3.6
 	go.opentelemetry.io/otel v1.11.1
 	go.opentelemetry.io/otel/exporters/jaeger v1.11.1
@@ -31,6 +33,7 @@ require (
 )
 
 require (
+	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed // indirect
 	github.com/bits-and-blooms/bitset v1.4.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
@@ -51,9 +54,12 @@ require (
 	github.com/pquerna/cachecontrol v0.1.0 // indirect
 	github.com/rogpeppe/go-internal v1.8.1 // indirect
 	github.com/santhosh-tekuri/jsonschema/v5 v5.1.0 // indirect
+	github.com/stoewer/go-strcase v1.2.0 // indirect
 	golang.org/x/sys v0.2.0 // indirect
 	golang.org/x/term v0.2.0 // indirect
 	golang.org/x/text v0.4.0 // indirect
+	google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 5372328..25e9b95 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,26 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221129172807-92be548d54be h1:KK6Dbr1rFPQSAyeWQGRUm7ba1K3XoxAmlFicLeR/6Ek=
 github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221129172807-92be548d54be/go.mod h1:RFja7smnRZpi5OPDPpW35TDK3SbHuAGCHs8wbNTSBxY=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed h1:ue9pVfIcP+QMEjfgo/Ez4ZjNZfonGgR6NgjMaJMu1Cg=
+github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
 github.com/ardanlabs/conf v1.5.0 h1:5TwP6Wu9Xi07eLFEpiCUF3oQXh9UzHMDVnD3u/I5d5c=
 github.com/ardanlabs/conf v1.5.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw=
 github.com/bits-and-blooms/bitset v1.4.0 h1:+YZ8ePm+He2pU3dZlIZiOeAKfrBkXi1lSrXJ/Xzgbu8=
 github.com/bits-and-blooms/bitset v1.4.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -16,6 +31,13 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
 github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
 github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
 github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -32,11 +54,37 @@ github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4
 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
 github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/cel-go v0.12.5 h1:DmzaiSgoaqGCjtpPQWl26/gND+yRpim56H1jCVev6d8=
+github.com/google/cel-go v0.12.5/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -87,6 +135,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
 github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
@@ -95,6 +145,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.1.0 h1:wSUNu/w/7OQ0Y3NVnfTU5uxzXY4uM
 github.com/santhosh-tekuri/jsonschema/v5 v5.1.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
 github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
@@ -106,6 +158,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+go.einride.tech/aip v0.60.0 h1:h6bgabZ5BCfAptbGex8jbh3VvPBRLa6xq+pQ1CAjHYw=
+go.einride.tech/aip v0.60.0/go.mod h1:SdLbSbgSU60Xkb4TMkmsZEQPHeEWx0ikBoq5QnqZvdg=
 go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
 go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
 go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
@@ -116,14 +170,44 @@ go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZp
 go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys=
 go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
 go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -132,11 +216,50 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I=
+google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -148,7 +271,11 @@ gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+a
 gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
 gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/magefile.go b/magefile.go
index 3784ad6..4c51f28 100644
--- a/magefile.go
+++ b/magefile.go
@@ -108,7 +108,12 @@ func Spec() error {
 		logrus.Fatal(err)
 		return err
 	}
-	return sh.Run(swagCommand, "init", "-g", "cmd/main.go", "--pd", "-o", "doc", "-ot", "yaml")
+	// One of the dependencies we have (antlr4) does not play nicely with swaggo, but we need to enable dependencies
+	// because many of our external API objects have ssi-sdk objects. We can work around this by setting depth. You can
+	// see a discussion of this topic in https://github.com/swaggo/swag/issues/948.
+	// We also set parseGoList because it's the only way parseDepth works until the following is fixed:
+	// https://github.com/swaggo/swag/issues/1269
+	return sh.Run(swagCommand, "init", "-g", "cmd/main.go", "--pd", "-o", "doc", "-ot", "yaml", "--parseDepth=3", "--parseGoList=false")
 }
 
 // Integration runs an integration test. Note: the ssi-service must be running for this to work
diff --git a/pkg/server/router/operation.go b/pkg/server/router/operation.go
index 544e5ea..f0e667d 100644
--- a/pkg/server/router/operation.go
+++ b/pkg/server/router/operation.go
@@ -9,6 +9,7 @@ import (
 	"github.com/tbd54566975/ssi-service/pkg/server/framework"
 	svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework"
 	"github.com/tbd54566975/ssi-service/pkg/service/operation"
+	"go.einride.tech/aip/filtering"
 	"net/http"
 )
 
@@ -47,15 +48,48 @@ type GetOperationsRequest struct {
 	Parent string `json:"parent"`
 
 	// A standard filter expression conforming to https://google.aip.dev/160.
-	// For example: 'status = done'.
+	// For example: `done = true`.
 	Filter string `json:"filter"`
 }
 
-func (r GetOperationsRequest) ToServiceRequest() operation.GetOperationsRequest {
-	return operation.GetOperationsRequest{
-		Parent: r.Parent,
-		Filter: r.Filter,
+func (r GetOperationsRequest) GetFilter() string {
+	return r.Filter
+}
+
+const (
+	DoneIdentifier = "done"
+	True           = "true"
+	False          = "false"
+)
+
+const FilterCharacterLimit = 1024
+
+func (r GetOperationsRequest) ToServiceRequest() (operation.GetOperationsRequest, error) {
+	var opReq operation.GetOperationsRequest
+
+	declarations, err := filtering.NewDeclarations(
+		filtering.DeclareFunction(filtering.FunctionEquals,
+			filtering.NewFunctionOverload(
+				filtering.FunctionOverloadEqualsString, filtering.TypeBool, filtering.TypeBool, filtering.TypeBool)),
+		filtering.DeclareIdent(DoneIdentifier, filtering.TypeBool),
+		filtering.DeclareIdent(True, filtering.TypeBool),
+		filtering.DeclareIdent(False, filtering.TypeBool),
+	)
+	if err != nil {
+		return opReq, errors.Wrap(err, "creating new filter declarations")
 	}
+
+	// Because parsing filters can be expensive, we limit is to a fixed len of chars. That should be more than enough
+	// for most use cases.
+	if len(r.GetFilter()) > FilterCharacterLimit {
+		return opReq, errors.Errorf("filter longer than %d character size limit", FilterCharacterLimit)
+	}
+	filter, err := filtering.ParseFilter(r, declarations)
+	if err != nil {
+		return opReq, errors.Wrap(err, "parsing filter")
+	}
+	opReq.Filter = filter
+	return opReq, nil
 }
 
 type GetOperationsResponse struct {
@@ -85,7 +119,11 @@ func (pdr OperationRouter) GetOperations(ctx context.Context, w http.ResponseWri
 			util.LoggingErrorMsg(err, "invalid get operations request"), http.StatusBadRequest)
 	}
 
-	req := request.ToServiceRequest()
+	req, err := request.ToServiceRequest()
+	if err != nil {
+		return framework.NewRequestError(
+			util.LoggingErrorMsg(err, "invalid get operations request"), http.StatusBadRequest)
+	}
 
 	ops, err := pdr.service.GetOperations(req)
 	if err != nil {
diff --git a/pkg/server/router/presentation.go b/pkg/server/router/presentation.go
index 603ba0f..b115f5e 100644
--- a/pkg/server/router/presentation.go
+++ b/pkg/server/router/presentation.go
@@ -14,6 +14,7 @@ import (
 	"github.com/tbd54566975/ssi-service/pkg/server/framework"
 	svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework"
 	"github.com/tbd54566975/ssi-service/pkg/service/presentation"
+	"go.einride.tech/aip/filtering"
 	"net/http"
 )
 
@@ -306,9 +307,15 @@ func (pr PresentationRouter) GetSubmission(ctx context.Context, w http.ResponseW
 }
 
 type ListSubmissionRequest struct {
+	// A standard filter expression conforming to https://google.aip.dev/160.
+	// For example: `status = "done"`.
 	Filter string `json:"filter"`
 }
 
+func (l ListSubmissionRequest) GetFilter() string {
+	return l.Filter
+}
+
 type ListSubmissionResponse struct {
 	Submissions []presentation.Submission `json:"submissions"`
 }
@@ -325,10 +332,43 @@ type ListSubmissionResponse struct {
 // @Failure      500  {string}  string  "Internal server error"
 // @Router       /v1/presentations/submissions [get]
 func (pr PresentationRouter) ListSubmissions(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
-	resp, err := pr.service.ListSubmissions(presentation.ListSubmissionRequest{})
+	var request ListSubmissionRequest
+	if err := framework.Decode(r, &request); err != nil {
+		return framework.NewRequestError(
+			util.LoggingErrorMsg(err, "invalid list submissions request"), http.StatusBadRequest)
+	}
+
+	const StatusIdentifier = "status"
+	declarations, err := filtering.NewDeclarations(
+		filtering.DeclareFunction(filtering.FunctionEquals,
+			filtering.NewFunctionOverload(
+				filtering.FunctionOverloadEqualsString, filtering.TypeBool, filtering.TypeString, filtering.TypeString)),
+		filtering.DeclareIdent(StatusIdentifier, filtering.TypeString),
+	)
+	if err != nil {
+		return framework.NewRequestError(
+			util.LoggingErrorMsg(err, "creating filter declarations"), http.StatusInternalServerError)
+	}
+
+	// Because parsing filters can be expensive, we limit is to a fixed len of chars. That should be more than enough
+	// for most use cases.
+	if len(request.GetFilter()) > FilterCharacterLimit {
+		err := errors.Errorf("filter longer than %d character size limit", FilterCharacterLimit)
+		return framework.NewRequestError(
+			util.LoggingErrorMsg(err, "invalid filter"), http.StatusBadRequest)
+
+	}
+	filter, err := filtering.ParseFilter(request, declarations)
+	if err != nil {
+		return framework.NewRequestError(
+			util.LoggingErrorMsg(err, "invalid filter"), http.StatusBadRequest)
+	}
+	resp, err := pr.service.ListSubmissions(presentation.ListSubmissionRequest{
+		Filter: filter,
+	})
 	if err != nil {
 		return framework.NewRequestError(
-			util.LoggingNewError("failed listing submissions"), http.StatusInternalServerError)
+			util.LoggingErrorMsg(err, "failed listing submissions"), http.StatusInternalServerError)
 	}
 	return framework.Respond(ctx, w, ListSubmissionResponse{Submissions: resp.Submissions}, http.StatusOK)
 }
diff --git a/pkg/service/operation/model.go b/pkg/service/operation/model.go
index 164b196..801ceab 100644
--- a/pkg/service/operation/model.go
+++ b/pkg/service/operation/model.go
@@ -1,6 +1,9 @@
 package operation
 
-import "strings"
+import (
+	"go.einride.tech/aip/filtering"
+	"strings"
+)
 
 type Result struct {
 	Error    string      `json:"error,omitempty"`
@@ -25,7 +28,7 @@ func SubmissionID(opID string) string {
 
 type GetOperationsRequest struct {
 	Parent string
-	Filter string
+	Filter filtering.Filter
 }
 
 type GetOperationsResponse struct {
diff --git a/pkg/service/operation/service.go b/pkg/service/operation/service.go
index 8e1272a..7ad0c60 100644
--- a/pkg/service/operation/service.go
+++ b/pkg/service/operation/service.go
@@ -33,7 +33,7 @@ func (s Service) Status() framework.Status {
 }
 
 func (s Service) GetOperations(request GetOperationsRequest) (*GetOperationsResponse, error) {
-	ops, err := s.storage.GetOperations()
+	ops, err := s.storage.GetOperations(request.Filter)
 	if err != nil {
 		return nil, errors.Wrap(err, "fetching ops from storage")
 	}
diff --git a/pkg/service/operation/storage/bolt.go b/pkg/service/operation/storage/bolt.go
index b74d0ca..4c39c8f 100644
--- a/pkg/service/operation/storage/bolt.go
+++ b/pkg/service/operation/storage/bolt.go
@@ -6,6 +6,7 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
+	"go.einride.tech/aip/filtering"
 )
 
 const (
@@ -43,18 +44,31 @@ func (b BoltOperationStorage) GetOperation(id string) (*StoredOperation, error)
 	return &stored, nil
 }
 
-func (b BoltOperationStorage) GetOperations() ([]StoredOperation, error) {
+func (b BoltOperationStorage) GetOperations(filter filtering.Filter) ([]StoredOperation, error) {
 	operations, err := b.db.ReadAll(namespace)
 	if err != nil {
 		return nil, util.LoggingErrorMsgf(err, "could not get all operations")
 	}
+
+	shouldInclude, err := storage.NewIncludeFunc(filter)
+	if err != nil {
+		return nil, err
+	}
 	stored := make([]StoredOperation, 0, len(operations))
 	for i, manifestBytes := range operations {
 		var nextOp StoredOperation
 		if err = json.Unmarshal(manifestBytes, &nextOp); err != nil {
 			logrus.WithError(err).WithField("idx", i).Warnf("Skipping operation")
 		}
-		stored = append(stored, nextOp)
+		include, err := shouldInclude(nextOp)
+		// We explicitly ignore evaluation errors and simply include them in the result.
+		if err != nil {
+			stored = append(stored, nextOp)
+			continue
+		}
+		if include {
+			stored = append(stored, nextOp)
+		}
 	}
 	return stored, nil
 }
diff --git a/pkg/service/operation/storage/storage.go b/pkg/service/operation/storage/storage.go
index a2c5b10..a4e90d3 100644
--- a/pkg/service/operation/storage/storage.go
+++ b/pkg/service/operation/storage/storage.go
@@ -3,6 +3,7 @@ package storage
 import (
 	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
+	"go.einride.tech/aip/filtering"
 )
 
 type StoredOperation struct {
@@ -18,10 +19,21 @@ type StoredOperation struct {
 	Response interface{} `json:"result,omitempty"`
 }
 
+func (s StoredOperation) FilterVariablesMap() map[string]interface{} {
+	return map[string]interface{}{
+		"done": s.Done,
+		// "true" and "false" are currently being parsed as identifiers, so we need to pass in the values that they
+		// evaluate to. Ideally, we should change them to be parsed as constants. That requires an upstream change in
+		// the filtering library.
+		"true":  true,
+		"false": false,
+	}
+}
+
 type Storage interface {
 	StoreOperation(op StoredOperation) error
 	GetOperation(id string) (*StoredOperation, error)
-	GetOperations() ([]StoredOperation, error)
+	GetOperations(filter filtering.Filter) ([]StoredOperation, error)
 	DeleteOperation(id string) error
 }
 
diff --git a/pkg/service/presentation/model.go b/pkg/service/presentation/model.go
index 42e71a0..3ab4d6c 100644
--- a/pkg/service/presentation/model.go
+++ b/pkg/service/presentation/model.go
@@ -6,6 +6,7 @@ import (
 	"github.com/TBD54566975/ssi-sdk/util"
 	"github.com/tbd54566975/ssi-service/internal/credential"
 	"github.com/tbd54566975/ssi-service/internal/keyaccess"
+	"go.einride.tech/aip/filtering"
 )
 
 type CreatePresentationDefinitionRequest struct {
@@ -61,6 +62,7 @@ type DeleteSubmissionRequest struct {
 }
 
 type ListSubmissionRequest struct {
+	Filter filtering.Filter
 }
 
 type Submission struct {
diff --git a/pkg/service/presentation/service.go b/pkg/service/presentation/service.go
index 29ae843..b8867c4 100644
--- a/pkg/service/presentation/service.go
+++ b/pkg/service/presentation/service.go
@@ -217,7 +217,7 @@ func (s Service) GetSubmission(request GetSubmissionRequest) (*GetSubmissionResp
 func (s Service) ListSubmissions(request ListSubmissionRequest) (*ListSubmissionResponse, error) {
 	logrus.Debug("listing presentation submissions")
 
-	subs, err := s.storage.ListSubmissions()
+	subs, err := s.storage.ListSubmissions(request.Filter)
 	if err != nil {
 		return nil, errors.Wrap(err, "fetching submissions from storage")
 	}
diff --git a/pkg/service/presentation/storage/bolt.go b/pkg/service/presentation/storage/bolt.go
index 516672a..e91d1c4 100644
--- a/pkg/service/presentation/storage/bolt.go
+++ b/pkg/service/presentation/storage/bolt.go
@@ -2,11 +2,11 @@ package storage
 
 import (
 	"fmt"
-	"github.com/tbd54566975/ssi-service/internal/util"
-
 	"github.com/goccy/go-json"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+	"github.com/tbd54566975/ssi-service/internal/util"
+	"go.einride.tech/aip/filtering"
 
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 )
@@ -20,19 +20,31 @@ type BoltPresentationStorage struct {
 	db *storage.BoltDB
 }
 
-func (b BoltPresentationStorage) ListSubmissions() ([]StoredSubmission, error) {
+func (b BoltPresentationStorage) ListSubmissions(filter filtering.Filter) ([]StoredSubmission, error) {
 	allData, err := b.db.ReadAll(submissionNamespace)
 	if err != nil {
 		return nil, errors.Wrap(err, "reading all data")
 	}
 
-	storedSubmissions := make([]StoredSubmission, len(allData))
-	i := 0
+	shouldInclude, err := storage.NewIncludeFunc(filter)
+	if err != nil {
+		return nil, err
+	}
+	storedSubmissions := make([]StoredSubmission, 0, len(allData))
 	for key, data := range allData {
-		if err := json.Unmarshal(data, &storedSubmissions[i]); err != nil {
-			logrus.WithError(err).WithField("key", key).Errorf("unmarshalling submission")
+		var ss StoredSubmission
+		if err = json.Unmarshal(data, &ss); err != nil {
+			logrus.WithError(err).WithField("key", key).Error("unmarshalling submission")
+		}
+		include, err := shouldInclude(ss)
+		// We explicitly ignore evaluation errors and simply include them in the result.
+		if err != nil {
+			storedSubmissions = append(storedSubmissions, ss)
+			continue
+		}
+		if include {
+			storedSubmissions = append(storedSubmissions, ss)
 		}
-		i++
 	}
 	return storedSubmissions, nil
 }
diff --git a/pkg/service/presentation/storage/storage.go b/pkg/service/presentation/storage/storage.go
index 8526de6..35099e5 100644
--- a/pkg/service/presentation/storage/storage.go
+++ b/pkg/service/presentation/storage/storage.go
@@ -5,6 +5,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
+	"go.einride.tech/aip/filtering"
 )
 
 type StoredPresentation struct {
@@ -66,10 +67,16 @@ type StoredSubmission struct {
 	Submission exchange.PresentationSubmission `json:"submission"`
 }
 
+func (s StoredSubmission) FilterVariablesMap() map[string]interface{} {
+	return map[string]interface{}{
+		"status": s.Status.String(),
+	}
+}
+
 type SubmissionStorage interface {
 	StoreSubmission(schema StoredSubmission) error
 	GetSubmission(id string) (*StoredSubmission, error)
-	ListSubmissions() ([]StoredSubmission, error)
+	ListSubmissions(filtering.Filter) ([]StoredSubmission, error)
 }
 
 var ErrSubmissionNotFound = errors.New("submission not found")
diff --git a/pkg/storage/filter.go b/pkg/storage/filter.go
new file mode 100644
index 0000000..0469025
--- /dev/null
+++ b/pkg/storage/filter.go
@@ -0,0 +1,69 @@
+package storage
+
+import (
+	"github.com/google/cel-go/cel"
+	"github.com/google/cel-go/common/types/ref"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+	"go.einride.tech/aip/filtering"
+)
+
+// FilterVarsMapper is an interface that encapsulates the FilterVariablesMap method. This interface is meant to be
+// implemented by any object that wants to include support for filtering.
+type FilterVarsMapper interface {
+	FilterVariablesMap() map[string]interface{}
+}
+
+// IncludeFunc is a function that given a mapper object, decides whether the object should be included in the result.
+type IncludeFunc func(FilterVarsMapper) (bool, error)
+
+// NewIncludeFunc creates an IncludeFunc given a filter object.
+// The result function is constructed as an evaluation of a cel program. The environment created matches standard
+// construction of a filter.
+// The result function runs the program evaluation on a given object that implements the FilterVarsMapper interface.
+// Evaluation errors bubbled up so clients can decide what to do.
+func NewIncludeFunc(filter filtering.Filter) (IncludeFunc, error) {
+	if filter.CheckedExpr == nil {
+		return func(_ FilterVarsMapper) (bool, error) {
+			return true, nil
+		}, nil
+	}
+
+	env, err := newCelEnv()
+	if err != nil {
+		return nil, errors.Wrap(err, "creating cel env")
+	}
+	ast := cel.CheckedExprToAst(filter.CheckedExpr)
+
+	program, err := env.Program(ast)
+	if err != nil {
+		return nil, errors.Wrap(err, "creating program from ast")
+	}
+	return func(f FilterVarsMapper) (bool, error) {
+		out, det, err := program.Eval(f.FilterVariablesMap())
+		if err != nil {
+			logrus.WithError(err).
+				WithField("details", det).
+				Error("evaluating submission")
+			return false, errors.Wrap(err, "evaluating program")
+		}
+		return out.Value().(bool), nil
+	}, nil
+}
+
+func simpleEquals(lhs ref.Val, rhs ref.Val) ref.Val {
+	return lhs.Equal(rhs)
+}
+
+func newCelEnv() (*cel.Env, error) {
+	return cel.NewEnv(
+		cel.Function("=",
+			cel.Overload("=_bool",
+				[]*cel.Type{cel.BoolType, cel.BoolType},
+				cel.BoolType,
+				cel.BinaryBinding(simpleEquals)),
+			cel.Overload("=_string",
+				[]*cel.Type{cel.StringType, cel.StringType},
+				cel.BoolType,
+				cel.BinaryBinding(simpleEquals))))
+}


Test patch:
diff --git a/pkg/server/router/did_test.go b/pkg/server/router/did_test.go
index 124d156..3ffcf38 100644
--- a/pkg/server/router/did_test.go
+++ b/pkg/server/router/did_test.go
@@ -130,8 +130,7 @@ func TestDIDRouter(t *testing.T) {
 		supported := didService.GetSupportedMethods()
 		assert.NotEmpty(tt, supported)
 		assert.Len(tt, supported.Methods, 2)
-		assert.Equal(tt, didsdk.KeyMethod, supported.Methods[0])
-		assert.Equal(tt, didsdk.WebMethod, supported.Methods[1])
+		assert.ElementsMatch(tt, supported.Methods, []didsdk.Method{didsdk.KeyMethod, didsdk.WebMethod})
 
 		// bad key type
 		_, err = didService.CreateDIDByMethod(did.CreateDIDRequest{Method: didsdk.WebMethod, KeyType: "bad", DIDWebID: "did:web:example.com"})
diff --git a/pkg/server/server_operation_test.go b/pkg/server/server_operation_test.go
index fabd169..7e01420 100644
--- a/pkg/server/server_operation_test.go
+++ b/pkg/server/server_operation_test.go
@@ -67,6 +67,55 @@ func TestOperationsAPI(t *testing.T) {
 				t.Errorf("Mismatch on submissions (-want +got):\n%s", diff)
 			}
 		})
+
+		t.Run("Returns operation when filtering to include", func(t *testing.T) {
+			s, err := storage.NewBoltDB()
+			assert.NoError(t, err)
+			pRouter := setupPresentationRouter(t, s)
+			opRouter := setupOperationsRouter(t, s)
+
+			def := createPresentationDefinition(t, pRouter)
+			holderSigner, holderDID := getSigner(t)
+			_ = createSubmission(t, pRouter, def.PresentationDefinition.ID, VerifiableCredential(), holderDID, holderSigner)
+
+			request := router.GetOperationsRequest{
+				Filter: "done = false",
+			}
+			value := newRequestValue(t, request)
+			req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/operations", value)
+			w := httptest.NewRecorder()
+
+			assert.NoError(t, opRouter.GetOperations(newRequestContext(), w, req))
+
+			var resp router.GetOperationsResponse
+			assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
+			assert.Len(t, resp.Operations, 1)
+			assert.False(t, resp.Operations[0].Done)
+		})
+
+		t.Run("Returns zero operations when filtering to exclude", func(t *testing.T) {
+			s, err := storage.NewBoltDB()
+			assert.NoError(t, err)
+			pRouter := setupPresentationRouter(t, s)
+			opRouter := setupOperationsRouter(t, s)
+
+			def := createPresentationDefinition(t, pRouter)
+			holderSigner, holderDID := getSigner(t)
+			_ = createSubmission(t, pRouter, def.PresentationDefinition.ID, VerifiableCredential(), holderDID, holderSigner)
+
+			request := router.GetOperationsRequest{
+				Filter: "done = true",
+			}
+			value := newRequestValue(t, request)
+			req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/operations", value)
+			w := httptest.NewRecorder()
+
+			assert.NoError(t, opRouter.GetOperations(newRequestContext(), w, req))
+
+			var resp router.GetOperationsResponse
+			assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
+			assert.Empty(t, resp.Operations)
+		})
 	})
 
 }
diff --git a/pkg/server/server_presentation_test.go b/pkg/server/server_presentation_test.go
index d161ca5..fad3665 100644
--- a/pkg/server/server_presentation_test.go
+++ b/pkg/server/server_presentation_test.go
@@ -293,6 +293,106 @@ func TestPresentationAPI(t *testing.T) {
 				t.Errorf("Mismatch on submissions (-want +got):\n%s", diff)
 			}
 		})
+
+		t.Run("bad filter returns error", func(t *testing.T) {
+			s, err := storage.NewBoltDB()
+			assert.NoError(t, err)
+			pRouter := setupPresentationRouter(t, s)
+			request := router.ListSubmissionRequest{
+				Filter: `im a baaad filter that's trying to break a lot of stuff'`,
+			}
+
+			value := newRequestValue(t, request)
+			req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/presentations/submissions", value)
+			w := httptest.NewRecorder()
+
+			err = pRouter.ListSubmissions(newRequestContext(), w, req)
+
+			require.Error(t, err)
+		})
+
+		t.Run("List submissions filters based on status", func(t *testing.T) {
+			s, err := storage.NewBoltDB()
+			assert.NoError(t, err)
+			pRouter := setupPresentationRouter(t, s)
+
+			holderSigner, holderDID := getSigner(t)
+			definition := createPresentationDefinition(t, pRouter)
+			op := createSubmission(t, pRouter, definition.PresentationDefinition.ID, VerifiableCredential(
+				WithCredentialSubject(credential.CredentialSubject{
+					"additionalName": "Mclovin",
+					"dateOfBirth":    "1987-01-02",
+					"familyName":     "Andres",
+					"givenName":      "Uribe",
+					"id":             "did:web:andresuribe.com",
+				})), holderDID, holderSigner)
+
+			request := router.ListSubmissionRequest{
+				Filter: `status="pending"`,
+			}
+
+			value := newRequestValue(t, request)
+			req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/presentations/submissions", value)
+			w := httptest.NewRecorder()
+
+			err = pRouter.ListSubmissions(newRequestContext(), w, req)
+
+			require.NoError(t, err)
+			var resp router.ListSubmissionResponse
+			assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
+
+			expectedSubmissions := []presentation.Submission{
+				{
+					Status: "pending",
+					PresentationSubmission: &exchange.PresentationSubmission{
+						ID:           operation.SubmissionID(op.ID),
+						DefinitionID: definition.PresentationDefinition.ID,
+					},
+				},
+			}
+			diff := cmp.Diff(expectedSubmissions, resp.Submissions,
+				cmpopts.IgnoreFields(exchange.PresentationSubmission{}, "DescriptorMap"),
+				cmpopts.SortSlices(func(l, r presentation.Submission) bool {
+					return l.PresentationSubmission.ID < r.PresentationSubmission.ID
+				}),
+			)
+			if diff != "" {
+				t.Errorf("Mismatch on submissions (-want +got):\n%s", diff)
+			}
+		})
+
+		t.Run("List submissions filter returns empty when status does not match", func(t *testing.T) {
+			s, err := storage.NewBoltDB()
+			assert.NoError(t, err)
+			pRouter := setupPresentationRouter(t, s)
+
+			holderSigner, holderDID := getSigner(t)
+			definition := createPresentationDefinition(t, pRouter)
+			_ = createSubmission(t, pRouter, definition.PresentationDefinition.ID, VerifiableCredential(
+				WithCredentialSubject(credential.CredentialSubject{
+					"additionalName": "Mclovin",
+					"dateOfBirth":    "1987-01-02",
+					"familyName":     "Andres",
+					"givenName":      "Uribe",
+					"id":             "did:web:andresuribe.com",
+				})), holderDID, holderSigner)
+
+			request := router.ListSubmissionRequest{
+				Filter: `status = "done"`,
+			}
+
+			value := newRequestValue(t, request)
+			req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/presentations/submissions", value)
+			w := httptest.NewRecorder()
+
+			err = pRouter.ListSubmissions(newRequestContext(), w, req)
+
+			require.NoError(t, err)
+			var resp router.ListSubmissionResponse
+			assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
+
+			assert.Empty(t, resp.Submissions)
+		})
 	})
 
 }