From 226502b9a390c40c08548cac8844655dc81585b7 Mon Sep 17 00:00:00 2001 From: Andrii Soluk Date: Fri, 8 Jan 2021 16:08:21 +0200 Subject: [PATCH] docs: Presentation Exchange - adds samples Signed-off-by: Andrii Soluk --- pkg/doc/presexch/README.md | 104 ++++++ pkg/doc/presexch/api_test.go | 3 +- pkg/doc/presexch/definition.go | 40 ++- pkg/doc/presexch/definition_test.go | 331 ++++------------- pkg/doc/presexch/example_test.go | 493 +++++++++++++++++++++++++- pkg/doc/verifiable/credential.go | 5 +- pkg/doc/verifiable/credential_test.go | 8 +- 7 files changed, 709 insertions(+), 275 deletions(-) create mode 100644 pkg/doc/presexch/README.md diff --git a/pkg/doc/presexch/README.md b/pkg/doc/presexch/README.md new file mode 100644 index 000000000..64a317b9d --- /dev/null +++ b/pkg/doc/presexch/README.md @@ -0,0 +1,104 @@ +# Presentation Exchange + +This document shows how to use [Presentation Exchange](https://identity.foundation/presentation-exchange) by examples. +Code examples can be found [here](example_test.go). + +1. This example demonstrates `predicate` and `limit_disclosure` usage. + The `presentation definition` below requires field `age` to be greater or equal to 18. + Also, we have `limit_disclosure=true` which requires that output data is limited to the entries specified in the `fields` property. + The `predicate` means that the result should be expressed as a boolean value. +```json +{ + "id": "31e2f0f1-6b70-411d-b239-56aed5321884", + "purpose": "To sell you a drink we need to know that you are an adult.", + "input_descriptors": [ + { + "id": "867bfe7a-5b91-46b2-9ba4-70028b8d9cc8", + "purpose": "Your age should be greater or equal to 18.", + "schema": [ + { + "uri": "https://www.w3.org/TR/vc-data-model/#types" + } + ], + "constraints": { + "limit_disclosure": true, + "fields": [ + { + "path": [ + "$.age" + ], + "filter": { + "type": "integer", + "minimum": 18 + }, + "predicate": "required" + } + ] + } + } + ] +} +``` +Let's say we have such a credential in our database. +```json +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "age": 21, + "credentialSchema": [ + { + "id": "https://www.w3.org/TR/vc-data-model/#types" + } + ], + "first_name": "Jesse", + "id": "2dc74354-e965-4883-be5e-bfec48bf60c7", + "issuer": "", + "last_name": "Pinkman", + "type": "VerifiableCredential" +} +``` +As a result, we will have the following `verifiable presentation`: +```json +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://identity.foundation/presentation-exchange/submission/v1" + ], + "presentation_submission": { + "id": "accd5adf-1dbf-4ed9-9ba2-d687476126cb", + "definition_id": "31e2f0f1-6b70-411d-b239-56aed5321884", + "descriptor_map": [ + { + "id": "867bfe7a-5b91-46b2-9ba4-70028b8d9cc8", + "format": "ldp_vp", + "path": "$.verifiableCredential[0]" + } + ] + }, + "type": [ + "VerifiablePresentation", + "PresentationSubmission" + ], + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "age": true, + "credentialSchema": [ + { + "id": "https://www.w3.org/TR/vc-data-model/#types" + } + ], + "credentialSubject": null, + "id": "2dc74354-e965-4883-be5e-bfec48bf60c7", + "issuer": "", + "type": "VerifiableCredential" + } + ] +} +``` + +As you can see the VP has a credential without `first_name` and `last_name` (because of `limit_disclosure`). +Also, instead of `age`, we have a boolean value (because of `predicate`). \ No newline at end of file diff --git a/pkg/doc/presexch/api_test.go b/pkg/doc/presexch/api_test.go index a2162a50f..6e3bebfb1 100644 --- a/pkg/doc/presexch/api_test.go +++ b/pkg/doc/presexch/api_test.go @@ -4,7 +4,7 @@ Copyright SecureKey Technologies Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package presexch +package presexch_test import ( "encoding/json" @@ -17,6 +17,7 @@ import ( "github.com/piprate/json-gold/ld" "github.com/stretchr/testify/require" + . "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" "github.com/hyperledger/aries-framework-go/pkg/doc/util" "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" ) diff --git a/pkg/doc/presexch/definition.go b/pkg/doc/presexch/definition.go index c63bd1586..69f7db305 100644 --- a/pkg/doc/presexch/definition.go +++ b/pkg/doc/presexch/definition.go @@ -11,6 +11,7 @@ import ( "encoding/json" "errors" "fmt" + "sort" "strings" "github.com/PaesslerAG/jsonpath" @@ -33,6 +34,8 @@ const ( Required Preference = "required" // Preferred predicate`s value. Preferred Preference = "preferred" + + tmpEnding = "tmp_unique_id_" ) // nolint: gochecknoglobals @@ -318,7 +321,7 @@ func (pd *PresentationDefinition) CreateVP(credentials ...*verifiable.Credential credentials, descriptors := merge(result) vp.CustomFields = verifiable.CustomFields{ - submissionProperty: PresentationSubmission{ + submissionProperty: &PresentationSubmission{ ID: uuid.New().String(), DefinitionID: pd.ID, DescriptorMap: descriptors, @@ -530,7 +533,7 @@ func filterConstraints(constraints *Constraints, creds []*verifiable.Credential) if constraints.LimitDisclosure { template, err = json.Marshal(map[string]interface{}{ - "id": credential.ID, + "id": tmpID(credential.ID), "credentialSchema": credential.Schemas, "type": credential.Types, "@context": credential.Context, @@ -557,6 +560,19 @@ func filterConstraints(constraints *Constraints, creds []*verifiable.Credential) return result, nil } +func tmpID(id string) string { + return id + tmpEnding + uuid.New().String() +} + +func trimTmpID(id string) string { + idx := strings.Index(id, tmpEnding) + if idx == -1 { + return id + } + + return id[:idx] +} + func createNewCredential(fs []*Field, src, limitedCred []byte) (*verifiable.Credential, error) { for _, f := range fs { paths, err := jsonpathkeys.ParsePaths(f.Path...) @@ -676,9 +692,19 @@ func merge(setOfCredentials map[string][]*verifiable.Credential) ([]*verifiable. descriptors []*InputDescriptorMapping ) - for descriptorID, credentials := range setOfCredentials { + keys := make([]string, 0, len(setOfCredentials)) + for k := range setOfCredentials { + keys = append(keys, k) + } + + sort.Strings(keys) + + for _, descriptorID := range keys { + credentials := setOfCredentials[descriptorID] + for _, credential := range credentials { if _, ok := setOfCreds[credential.ID]; !ok { + credential.ID = trimTmpID(credential.ID) result = append(result, credential) setOfCreds[credential.ID] = len(descriptors) } @@ -694,9 +720,17 @@ func merge(setOfCredentials map[string][]*verifiable.Credential) ([]*verifiable. } } + sort.Sort(byID(descriptors)) + return result, descriptors } +type byID []*InputDescriptorMapping + +func (a byID) Len() int { return len(a) } +func (a byID) Less(i, j int) bool { return a[i].ID < a[j].ID } +func (a byID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + func credentialsToInterface(credentials []*verifiable.Credential) []interface{} { var result []interface{} for i := range credentials { diff --git a/pkg/doc/presexch/definition_test.go b/pkg/doc/presexch/definition_test.go index f1ffb5aab..cd90b9d77 100644 --- a/pkg/doc/presexch/definition_test.go +++ b/pkg/doc/presexch/definition_test.go @@ -34,211 +34,21 @@ var ( strFilterType = "string" arrFilterType = "array" intFilterType = "integer" -) - -const defaultVCSchema = `{ - "required": [ - "@context", - "type", - "credentialSubject", - "issuer", - "issuanceDate" - ], - "properties": { - "@context": { - "oneOf": [ - { - "type": "string", - "const": "https://www.w3.org/2018/credentials/v1" - }, - { - "type": "array", - "items": [ - { - "type": "string", - "const": "https://www.w3.org/2018/credentials/v1" - } - ], - "uniqueItems": true, - "additionalItems": { - "oneOf": [ - { - "type": "object" - }, - { - "type": "string" - } - ] - } - } - ] - }, - "id": { - "type": "string", - "format": "uri" - }, - "type": { - "oneOf": [ - { - "type": "array", - "minItems": 1, - "contains": { - "type": "string", - "pattern": "^VerifiableCredential$" - } - }, - { - "type": "string", - "pattern": "^VerifiableCredential$" - } - ] - }, - "credentialSubject": { - "anyOf": [ - { - "type": "array" - }, - { - "type": "object" - }, - { - "type": "string" - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "string", - "format": "uri" - }, - { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "string", - "format": "uri" - } - } - } - ] - }, - "issuanceDate": { - "type": "string", - "format": "date-time" - }, - "proof": { - "anyOf": [ - { - "$ref": "#/definitions/proof" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/proof" - } - }, - { - "type": "null" - } - ] - }, - "expirationDate": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "credentialStatus": { - "$ref": "#/definitions/typedID" - }, - "credentialSchema": { - "$ref": "#/definitions/typedIDs" - }, - "evidence": { - "$ref": "#/definitions/typedIDs" - }, - "refreshService": { - "$ref": "#/definitions/typedID" - } - }, - "definitions": { - "typedID": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "object", - "required": [ - "id", - "type" - ], - "properties": { - "id": { - "type": "string", - "format": "uri" - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - } - } - ] - }, - "typedIDs": { - "anyOf": [ - { - "$ref": "#/definitions/typedID" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/typedID" - } - }, - { - "type": "null" - } - ] - }, - "proof": { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string" - } - } - } - } -} -` -var testServer *httptest.Server // nolint: gochecknoglobals + subIsIssuerRequired = Required + // schemaURI is being set in init() function. + schemaURI string +) // nolint: gochecknoinits func init() { - testServer = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) - _, _ = res.Write([]byte(defaultVCSchema)) //nolint:errcheck + //nolint: gosec,errcheck + res.Write([]byte(verifiable.DefaultSchema)) })) + + schemaURI = server.URL } func TestPresentationDefinition_IsValid(t *testing.T) { @@ -274,7 +84,6 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) t.Run("Checks submission requirements", func(t *testing.T) { - subjectIsIssuer := Required issuerID := uuid.New().String() pd := &PresentationDefinition{ @@ -308,10 +117,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"A"}, Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, Fields: []*Field{{ Path: []string{"$.first_name", "$.last_name"}, }}, @@ -320,10 +129,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"child"}, Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, Fields: []*Field{{ Path: []string{"$.age"}, Filter: &Filter{ @@ -337,10 +146,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"teenager"}, Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, Fields: []*Field{{ Path: []string{"$.age"}, Filter: &Filter{ @@ -354,10 +163,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"adult"}, Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, Fields: []*Field{{ Path: []string{"$.age"}, Filter: &Filter{ @@ -373,7 +182,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -384,7 +193,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: []verifiable.Subject{{ID: issuerID}}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -402,7 +211,6 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) t.Run("Checks submission requirements (no descriptor)", func(t *testing.T) { - subjectIsIssuer := Required issuerID := uuid.New().String() pd := &PresentationDefinition{ @@ -427,10 +235,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"A"}, Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, Fields: []*Field{{ Path: []string{"$.first_name", "$.last_name"}, }}, @@ -441,7 +249,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -452,7 +260,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: []verifiable.Subject{{ID: issuerID}}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -473,7 +281,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -490,7 +298,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Context: []string{"https://www.w3.org/2018/credentials/v1"}, Types: []string{"VerifiableCredential"}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, Subject: "did:example:76e12ec712ebc6f1c221ebfeb1f", @@ -530,7 +338,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ LimitDisclosure: true, @@ -548,7 +356,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Context: []string{"https://www.w3.org/2018/credentials/v1"}, Types: []string{"VerifiableCredential"}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, Subject: "did:example:76e12ec712ebc6f1c221ebfeb1f", @@ -588,7 +396,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -602,7 +410,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -621,7 +429,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -634,7 +442,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -643,7 +451,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, CustomFields: map[string]interface{}{ "last_name": "Travis", @@ -660,7 +468,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -675,7 +483,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -684,7 +492,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, CustomFields: map[string]interface{}{ "last_name": "Travis", @@ -696,7 +504,6 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) t.Run("Matches one credentials (two fields)", func(t *testing.T) { - subjectIsIssuer := Required issuerID := uuid.New().String() pd := &PresentationDefinition{ @@ -704,10 +511,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, Fields: []*Field{{ Path: []string{"$.first_name"}, Filter: &Filter{Type: &strFilterType}, @@ -723,7 +530,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Subject: map[string]interface{}{}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -734,7 +541,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: verifiable.Subject{ID: issuerID}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -751,7 +558,6 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) t.Run("Matches one credentials (three fields - disclosure)", func(t *testing.T) { - subjectIsIssuer := Required issuerID := "did:example:76e12ec712ebc6f1c221ebfeb1f" pd := &PresentationDefinition{ @@ -759,10 +565,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, LimitDisclosure: true, Fields: []*Field{{ Path: []string{"$.first_name"}, @@ -787,7 +593,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: []map[string]interface{}{{}}, Issuer: verifiable.Issuer{ID: uuid.New().String()}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -800,7 +606,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: []map[string]interface{}{{"id": issuerID}}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, Issued: &util.TimeWithTrailingZeroMsec{ @@ -873,7 +679,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ LimitDisclosure: true, @@ -892,7 +698,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Issuer: verifiable.Issuer{CustomFields: map[string]interface{}{"k": "v"}}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -906,7 +712,6 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) t.Run("Matches one credentials (field pattern)", func(t *testing.T) { - subjectIsIssuer := Required issuerID := uuid.New().String() pd := &PresentationDefinition{ @@ -914,10 +719,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, Fields: []*Field{{ Path: []string{"$.first_name"}, Filter: &Filter{ @@ -934,7 +739,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: map[string]interface{}{"id": issuerID}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -944,7 +749,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Subject: map[string]interface{}{"id": 123}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, CustomFields: map[string]interface{}{ "first_name": "Travis", @@ -961,7 +766,6 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) t.Run("Matches one credentials", func(t *testing.T) { - subjectIsIssuer := Required issuerID := uuid.New().String() pd := &PresentationDefinition{ @@ -969,10 +773,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, Fields: []*Field{{ Path: []string{"$.first_name", "$.last_name"}, }}, @@ -983,7 +787,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -994,7 +798,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: []verifiable.Subject{{ID: issuerID}}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -1011,7 +815,6 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) t.Run("Matches one credentials (two descriptors)", func(t *testing.T) { - subjectIsIssuer := Required issuerID := uuid.New().String() pd := &PresentationDefinition{ @@ -1019,10 +822,10 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ - SubjectIsIssuer: &subjectIsIssuer, + SubjectIsIssuer: &subIsIssuerRequired, Fields: []*Field{{ Path: []string{"$.first_name", "$.last_name"}, }}, @@ -1030,7 +833,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }, { ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -1045,7 +848,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: issuerID, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -1056,7 +859,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: issuerID, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -1078,7 +881,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -1091,7 +894,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ @@ -1100,7 +903,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -1122,7 +925,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, }}, } @@ -1130,13 +933,13 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, }}, }) @@ -1154,7 +957,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: testServer.URL, + URI: schemaURI, }}, }}, } @@ -1162,7 +965,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: testServer.URL, + ID: schemaURI, Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ @@ -1401,7 +1204,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { func checkSubmission(t *testing.T, vp *verifiable.Presentation, pd *PresentationDefinition) { t.Helper() - ps, ok := vp.CustomFields["presentation_submission"].(PresentationSubmission) + ps, ok := vp.CustomFields["presentation_submission"].(*PresentationSubmission) require.True(t, ok) require.NotEmpty(t, ps.ID) require.Equal(t, ps.DefinitionID, pd.ID) diff --git a/pkg/doc/presexch/example_test.go b/pkg/doc/presexch/example_test.go index 1a2958d1d..2646e2f54 100644 --- a/pkg/doc/presexch/example_test.go +++ b/pkg/doc/presexch/example_test.go @@ -4,18 +4,509 @@ Copyright SecureKey Technologies Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package presexch +package presexch_test import ( "encoding/json" "fmt" "strings" + "time" "github.com/piprate/json-gold/ld" + . "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" + "github.com/hyperledger/aries-framework-go/pkg/doc/util" "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" ) +const dummy = "DUMMY" + +func ExamplePresentationDefinition_CreateVP() { + predicate := Required + + pd := &PresentationDefinition{ + ID: "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + Purpose: "To sell you a drink we need to know that you are an adult.", + InputDescriptors: []*InputDescriptor{{ + ID: "age_descriptor", + Purpose: "Your age should be greater or equal to 18.", + Schema: []*Schema{{ + URI: schemaURI, + }}, + Constraints: &Constraints{ + LimitDisclosure: true, + Fields: []*Field{{ + Path: []string{"$.age"}, + Predicate: &predicate, + Filter: &Filter{ + Type: &intFilterType, + Minimum: 18, + }, + }}, + }, + }}, + } + + vp, err := pd.CreateVP(&verifiable.Credential{ + ID: "http://example.edu/credentials/777", + Context: []string{"https://www.w3.org/2018/credentials/v1"}, + Types: []string{"VerifiableCredential"}, + Issuer: verifiable.Issuer{ + ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", + }, + Issued: &util.TimeWithTrailingZeroMsec{ + Time: time.Time{}, + }, + Subject: "did:example:76e12ec712ebc6f1c221ebfeb1f", + Schemas: []verifiable.TypedID{{ + ID: schemaURI, + Type: "JsonSchemaValidator2018", + }}, + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Pinkman", + "age": 21, + }, + }) + + vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy + vp.Credentials()[0].(*verifiable.Credential).Schemas[0].ID = dummy + + if err != nil { + panic(err) + } + + vpBytes, err := json.MarshalIndent(vp, "", "\t") + if err != nil { + panic(err) + } + + fmt.Println(string(vpBytes)) + // Output: + //{ + // "@context": [ + // "https://www.w3.org/2018/credentials/v1", + // "https://identity.foundation/presentation-exchange/submission/v1" + // ], + // "presentation_submission": { + // "id": "DUMMY", + // "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + // "descriptor_map": [ + // { + // "id": "age_descriptor", + // "format": "ldp_vp", + // "path": "$.verifiableCredential[0]" + // } + // ] + // }, + // "type": [ + // "VerifiablePresentation", + // "PresentationSubmission" + // ], + // "verifiableCredential": [ + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "age": true, + // "credentialSchema": [ + // { + // "id": "DUMMY", + // "type": "JsonSchemaValidator2018" + // } + // ], + // "credentialSubject": "did:example:76e12ec712ebc6f1c221ebfeb1f", + // "id": "http://example.edu/credentials/777", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f", + // "type": "VerifiableCredential" + // } + // ] + //} +} + +func ExamplePresentationDefinition_CreateVP_multipleMatches() { + pd := &PresentationDefinition{ + ID: "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + Purpose: "To sell you a drink we need to know that you are an adult.", + InputDescriptors: []*InputDescriptor{{ + ID: "age_descriptor", + Purpose: "Your age should be greater or equal to 18.", + Schema: []*Schema{{ + URI: schemaURI, + }}, + Constraints: &Constraints{ + Fields: []*Field{{ + Path: []string{"$.age"}, + Filter: &Filter{ + Type: &intFilterType, + Minimum: 18, + }, + }}, + }, + }, { + ID: "first_name_descriptor", + Purpose: "First name must be either Andrew or Jesse", + Schema: []*Schema{{ + URI: schemaURI, + }}, + Constraints: &Constraints{ + Fields: []*Field{{ + Path: []string{"$.first_name"}, + Filter: &Filter{ + Type: &strFilterType, + Pattern: "Andrew|Jesse", + }, + }}, + }, + }}, + } + + vp, err := pd.CreateVP(&verifiable.Credential{ + ID: "http://example.edu/credentials/777", + Context: []string{"https://www.w3.org/2018/credentials/v1"}, + Types: []string{"VerifiableCredential"}, + Issuer: verifiable.Issuer{ + ID: "did:example:777", + }, + Issued: &util.TimeWithTrailingZeroMsec{ + Time: time.Time{}, + }, + Subject: "did:example:777", + Schemas: []verifiable.TypedID{{ + ID: schemaURI, + Type: "JsonSchemaValidator2018", + }}, + CustomFields: map[string]interface{}{ + "first_name": "Andrew", + "last_name": "Hanks", + "age": 25, + }, + }, &verifiable.Credential{ + ID: "http://example.edu/credentials/888", + Context: []string{"https://www.w3.org/2018/credentials/v1"}, + Types: []string{"VerifiableCredential"}, + Issuer: verifiable.Issuer{ + ID: "did:example:888", + }, + Issued: &util.TimeWithTrailingZeroMsec{ + Time: time.Time{}, + }, + Subject: "did:example:888", + Schemas: []verifiable.TypedID{{ + ID: schemaURI, + Type: "JsonSchemaValidator2018", + }}, + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Pinkman", + "age": 21, + }, + }) + + vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy + + for _, credential := range vp.Credentials() { + credential.(*verifiable.Credential).Schemas[0].ID = dummy + } + + if err != nil { + panic(err) + } + + vpBytes, err := json.MarshalIndent(vp, "", "\t") + if err != nil { + panic(err) + } + + fmt.Println(string(vpBytes)) + // Output: + //{ + // "@context": [ + // "https://www.w3.org/2018/credentials/v1", + // "https://identity.foundation/presentation-exchange/submission/v1" + // ], + // "presentation_submission": { + // "id": "DUMMY", + // "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + // "descriptor_map": [ + // { + // "id": "age_descriptor", + // "format": "ldp_vp", + // "path": "$.verifiableCredential[0]" + // }, + // { + // "id": "age_descriptor", + // "format": "ldp_vp", + // "path": "$.verifiableCredential[1]" + // }, + // { + // "id": "first_name_descriptor", + // "format": "ldp_vp", + // "path": "$.verifiableCredential[0]" + // }, + // { + // "id": "first_name_descriptor", + // "format": "ldp_vp", + // "path": "$.verifiableCredential[1]" + // } + // ] + // }, + // "type": [ + // "VerifiablePresentation", + // "PresentationSubmission" + // ], + // "verifiableCredential": [ + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "age": 25, + // "credentialSchema": [ + // { + // "id": "DUMMY", + // "type": "JsonSchemaValidator2018" + // } + // ], + // "credentialSubject": "did:example:777", + // "first_name": "Andrew", + // "id": "http://example.edu/credentials/777", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:777", + // "last_name": "Hanks", + // "type": "VerifiableCredential" + // }, + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "age": 21, + // "credentialSchema": [ + // { + // "id": "DUMMY", + // "type": "JsonSchemaValidator2018" + // } + // ], + // "credentialSubject": "did:example:888", + // "first_name": "Jesse", + // "id": "http://example.edu/credentials/888", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:888", + // "last_name": "Pinkman", + // "type": "VerifiableCredential" + // } + // ] + //} +} + +func ExamplePresentationDefinition_CreateVP_multipleMatchesDisclosure() { + pd := &PresentationDefinition{ + ID: "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + Purpose: "To sell you a drink we need to know that you are an adult.", + InputDescriptors: []*InputDescriptor{{ + ID: "age_descriptor", + Purpose: "Your age should be greater or equal to 18.", + Schema: []*Schema{{ + URI: schemaURI, + }}, + Constraints: &Constraints{ + Fields: []*Field{{ + Path: []string{"$.age"}, + Filter: &Filter{ + Type: &intFilterType, + Minimum: 18, + }, + }}, + }, + }, { + ID: "first_name_descriptor", + Purpose: "First name must be either Andrew or Jesse", + Schema: []*Schema{{ + URI: schemaURI, + }}, + Constraints: &Constraints{ + LimitDisclosure: true, + Fields: []*Field{{ + Path: []string{"$.first_name"}, + Filter: &Filter{ + Type: &strFilterType, + Pattern: "Andrew|Jesse", + }, + }}, + }, + }}, + } + + vp, err := pd.CreateVP(&verifiable.Credential{ + ID: "http://example.edu/credentials/777", + Context: []string{"https://www.w3.org/2018/credentials/v1"}, + Types: []string{"VerifiableCredential"}, + Issuer: verifiable.Issuer{ + ID: "did:example:777", + }, + Issued: &util.TimeWithTrailingZeroMsec{ + Time: time.Time{}, + }, + Subject: "did:example:777", + Schemas: []verifiable.TypedID{{ + ID: schemaURI, + Type: "JsonSchemaValidator2018", + }}, + CustomFields: map[string]interface{}{ + "first_name": "Andrew", + "last_name": "Hanks", + "age": 25, + }, + }, &verifiable.Credential{ + ID: "http://example.edu/credentials/888", + Context: []string{"https://www.w3.org/2018/credentials/v1"}, + Types: []string{"VerifiableCredential"}, + Issuer: verifiable.Issuer{ + ID: "did:example:888", + }, + Issued: &util.TimeWithTrailingZeroMsec{ + Time: time.Time{}, + }, + Subject: "did:example:888", + Schemas: []verifiable.TypedID{{ + ID: schemaURI, + Type: "JsonSchemaValidator2018", + }}, + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Pinkman", + "age": 21, + }, + }) + if err != nil { + panic(err) + } + + vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy + + for _, credential := range vp.Credentials() { + credential.(*verifiable.Credential).Schemas[0].ID = dummy + } + + vpBytes, err := json.MarshalIndent(vp, "", "\t") + if err != nil { + panic(err) + } + + fmt.Println(string(vpBytes)) + // Output: + //{ + // "@context": [ + // "https://www.w3.org/2018/credentials/v1", + // "https://identity.foundation/presentation-exchange/submission/v1" + // ], + // "presentation_submission": { + // "id": "DUMMY", + // "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + // "descriptor_map": [ + // { + // "id": "age_descriptor", + // "format": "ldp_vp", + // "path": "$.verifiableCredential[0]" + // }, + // { + // "id": "age_descriptor", + // "format": "ldp_vp", + // "path": "$.verifiableCredential[1]" + // }, + // { + // "id": "first_name_descriptor", + // "format": "ldp_vp", + // "path": "$.verifiableCredential[2]" + // }, + // { + // "id": "first_name_descriptor", + // "format": "ldp_vp", + // "path": "$.verifiableCredential[3]" + // } + // ] + // }, + // "type": [ + // "VerifiablePresentation", + // "PresentationSubmission" + // ], + // "verifiableCredential": [ + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "age": 25, + // "credentialSchema": [ + // { + // "id": "DUMMY", + // "type": "JsonSchemaValidator2018" + // } + // ], + // "credentialSubject": "did:example:777", + // "first_name": "Andrew", + // "id": "http://example.edu/credentials/777", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:777", + // "last_name": "Hanks", + // "type": "VerifiableCredential" + // }, + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "age": 21, + // "credentialSchema": [ + // { + // "id": "DUMMY", + // "type": "JsonSchemaValidator2018" + // } + // ], + // "credentialSubject": "did:example:888", + // "first_name": "Jesse", + // "id": "http://example.edu/credentials/888", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:888", + // "last_name": "Pinkman", + // "type": "VerifiableCredential" + // }, + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "credentialSchema": [ + // { + // "id": "DUMMY", + // "type": "JsonSchemaValidator2018" + // } + // ], + // "credentialSubject": "did:example:777", + // "first_name": "Andrew", + // "id": "http://example.edu/credentials/777", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:777", + // "type": "VerifiableCredential" + // }, + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "credentialSchema": [ + // { + // "id": "DUMMY", + // "type": "JsonSchemaValidator2018" + // } + // ], + // "credentialSubject": "did:example:888", + // "first_name": "Jesse", + // "id": "http://example.edu/credentials/888", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:888", + // "type": "VerifiableCredential" + // } + // ] + //} +} + // Example of a Verifier verifying the presentation submission of a Holder. func ExamplePresentationDefinition_Match() { // verifier sends their presentation definitions to the holder diff --git a/pkg/doc/verifiable/credential.go b/pkg/doc/verifiable/credential.go index fb24c9b16..4757eb77f 100644 --- a/pkg/doc/verifiable/credential.go +++ b/pkg/doc/verifiable/credential.go @@ -26,7 +26,8 @@ import ( var logger = log.New("aries-framework/doc/verifiable") -const defaultSchema = `{ +// DefaultSchema describes default schema. +const DefaultSchema = `{ "required": [ "@context", "type", @@ -1239,7 +1240,7 @@ func getSchemaLoader(schemas []TypedID, opts *credentialOpts) (gojsonschema.JSON } func defaultSchemaLoader() gojsonschema.JSONLoader { - return gojsonschema.NewStringLoader(defaultSchema) + return gojsonschema.NewStringLoader(DefaultSchema) } func getJSONSchema(url string, opts *credentialOpts) ([]byte, error) { diff --git a/pkg/doc/verifiable/credential_test.go b/pkg/doc/verifiable/credential_test.go index 479e2d318..1865c4289 100644 --- a/pkg/doc/verifiable/credential_test.go +++ b/pkg/doc/verifiable/credential_test.go @@ -765,7 +765,7 @@ func TestWithDisabledProofCheck(t *testing.T) { func TestWithCredentialSchemaLoader(t *testing.T) { httpClient := &http.Client{} - jsonSchemaLoader := gojsonschema.NewStringLoader(defaultSchema) + jsonSchemaLoader := gojsonschema.NewStringLoader(DefaultSchema) cache := NewExpirableSchemaCache(100, 10*time.Minute) credentialOpt := WithCredentialSchemaLoader( @@ -876,7 +876,7 @@ func TestWithEmbeddedSignatureSuites(t *testing.T) { func TestCustomCredentialJsonSchemaValidator2018(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { rawMap := make(map[string]interface{}) - require.NoError(t, json.Unmarshal([]byte(defaultSchema), &rawMap)) + require.NoError(t, json.Unmarshal([]byte(DefaultSchema), &rawMap)) // extend default schema to require new referenceNumber field to be mandatory required, success := rawMap["required"].([]interface{}) @@ -993,7 +993,7 @@ func TestDownloadCustomSchema(t *testing.T) { noCacheOpts := &credentialOpts{schemaLoader: newDefaultSchemaLoader()} withCacheOpts := &credentialOpts{schemaLoader: &CredentialSchemaLoader{ schemaDownloadClient: httpClient, - jsonLoader: gojsonschema.NewStringLoader(defaultSchema), + jsonLoader: gojsonschema.NewStringLoader(DefaultSchema), cache: NewExpirableSchemaCache(32*1024*1024, time.Hour), }} @@ -1035,7 +1035,7 @@ func TestDownloadCustomSchema(t *testing.T) { // Check for cache expiration. withCacheOpts = &credentialOpts{schemaLoader: &CredentialSchemaLoader{ schemaDownloadClient: httpClient, - jsonLoader: gojsonschema.NewStringLoader(defaultSchema), + jsonLoader: gojsonschema.NewStringLoader(DefaultSchema), cache: NewExpirableSchemaCache(32*1024*1024, time.Second), }} loadsCount = 0