-
Notifications
You must be signed in to change notification settings - Fork 204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(core)!: metadata on records #505
feat(core)!: metadata on records #505
Conversation
Signed-off-by: Berend Sliedrecht <[email protected]>
Signed-off-by: Berend Sliedrecht <[email protected]>
Signed-off-by: Berend Sliedrecht <[email protected]>
Signed-off-by: Berend Sliedrecht <[email protected]>
Signed-off-by: Berend Sliedrecht <[email protected]>
Signed-off-by: Berend Sliedrecht <[email protected]>
Signed-off-by: Berend Sliedrecht <[email protected]>
Codecov Report
@@ Coverage Diff @@
## main #505 +/- ##
==========================================
+ Coverage 86.42% 86.49% +0.06%
==========================================
Files 266 267 +1
Lines 5733 5762 +29
Branches 923 932 +9
==========================================
+ Hits 4955 4984 +29
Misses 777 777
Partials 1 1
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good @blu3beri. Only thing I'm not sure about is the checks in the credential record constructor
if (props.metadata) { | ||
if (props.metadata.requestMetadata) { | ||
this.metadata.set('requestMetadata', props.metadata.requestMetadata) | ||
} | ||
if (props.metadata.schemaId) { | ||
this.metadata.add('indyCredentialMetadata', { | ||
schemaId: props.metadata.schemaId, | ||
}) | ||
} | ||
if (props.metadata.credentialDefinitionId) { | ||
this.metadata.add('indyCredentialMetadata', { | ||
credentialDefinitionId: props.metadata.credentialDefinitionId, | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We haven't reached v0.1.0 yet so maybe it would be easier to just see this as a breaking change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you propose to do this? Some part of the framework (in credentialService.ts
) is dependent on these fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just let them call the metadata themselves
const record = new CredentialRecord({ /** config props */ })
record.metadata.set('indyCredentialMetadata', {
schemaId: '',
credentialDefinitionId: ''
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be wise to increment our version in some manner to indicate a breaking change here, since this could cause fairly significant issues for existing systems? Is there harm in utilizing @blu3beri's original mechanism until a later date?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @blu3beri's approach wouldn't work out of the box. The constructor is not called with properties when a record is retrieved from storage. We could add a custom transformer, but I'd rather only take these things into account between stable releases.
Maybe we can look at writing transform scripts. I think we should be very cautious with adding these if statements to the codebase. I don't want to be stuck with this piece of code for eternity. We can never assume when all records are transformed. We're going to have a lot more breaking changes going forward that I'll rather solve by writing update scripts. I'd day that is a reasonable for pre-1.0 releases
Maybe we can keep it in here till 0.1.0. Then for 0.1.0 we provide scripts, and for 0.2.0 we again provide a script for the transformation.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I am misunderstanding your point, but right now we set this data in the constructor already and the credentialService
, line 612 and 627, relay on these fields. It will even error out when we do not have these fields in the metadata.
I am okay with leaving this out, but how do you propose we handle that error case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay we were talking about two different things here. For your point @blu3beri, I suggest we remove the metadata from the constructor and require it to be set from using the record.metadata.set
afterwards.
What I was mainly thinking about here is the persistence. The metadata on the current credential records has a different structure than the metadata we're adding in this PR. My thinking was that we add something like this migration script called 0.1.0-alpha.291-0.1.0-alpha.292.ts
export async function migrate291To292(agent: Agent) {
const credentialRecords = await agent.credentials.getAll();
const credentialRepository = await agent.injectionContainer.resolve(
CredentialRepository
);
for (const credentialRecord of credentialRecords) {
const data = credentialRecord.metadata.data;
const metadata = MetaData();
if (data.requestMetadata) {
metadata.set("indyRequestMetadata", data.requestMetadata);
}
if (data.schemaId) {
this.metadata.add("indyCredentialMetadata", {
schemaId: data.schemaId,
});
}
if (data.credentialDefinitionId) {
this.metadata.add("indyCredentialMetadata", {
credentialDefinitionId: data.credentialDefinitionId,
});
}
credentialRecord.metadata = metadata;
await credentialRepository.update(credentialRecord);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh okay. I am in favour of the migration scripts, but for the 0.1.0 and so on. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'd be in favor of migration scripts, as that'd be very nice for breaking changes. We could either tackle that now or at a 0.1.0 release, as you mentioned. :)
I'll admit I'm not entirely sure how that should be handled from a mobile app perspective, but that can be figured out. :)
packages/core/src/modules/credentials/services/CredentialService.ts
Outdated
Show resolved
Hide resolved
if (props.metadata) { | ||
if (props.metadata.requestMetadata) { | ||
this.metadata.set('requestMetadata', props.metadata.requestMetadata) | ||
} | ||
if (props.metadata.schemaId) { | ||
this.metadata.add('indyCredentialMetadata', { | ||
schemaId: props.metadata.schemaId, | ||
}) | ||
} | ||
if (props.metadata.credentialDefinitionId) { | ||
this.metadata.add('indyCredentialMetadata', { | ||
credentialDefinitionId: props.metadata.credentialDefinitionId, | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay we were talking about two different things here. For your point @blu3beri, I suggest we remove the metadata from the constructor and require it to be set from using the record.metadata.set
afterwards.
What I was mainly thinking about here is the persistence. The metadata on the current credential records has a different structure than the metadata we're adding in this PR. My thinking was that we add something like this migration script called 0.1.0-alpha.291-0.1.0-alpha.292.ts
export async function migrate291To292(agent: Agent) {
const credentialRecords = await agent.credentials.getAll();
const credentialRepository = await agent.injectionContainer.resolve(
CredentialRepository
);
for (const credentialRecord of credentialRecords) {
const data = credentialRecord.metadata.data;
const metadata = MetaData();
if (data.requestMetadata) {
metadata.set("indyRequestMetadata", data.requestMetadata);
}
if (data.schemaId) {
this.metadata.add("indyCredentialMetadata", {
schemaId: data.schemaId,
});
}
if (data.credentialDefinitionId) {
this.metadata.add("indyCredentialMetadata", {
credentialDefinitionId: data.credentialDefinitionId,
});
}
credentialRecord.metadata = metadata;
await credentialRepository.update(credentialRecord);
}
}
Signed-off-by: Berend Sliedrecht <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
So, just to confirm, this would have a breaking change at this point still, right? I have some cases that would need a way to migrate--so this would be a spot that would need a migration script, right? |
Yes, a migration script from all the previous versions to this one is required. @TimoGlastra gave one in the comments above that should, or maybe with some minor tweaking. |
Signed-off-by: Berend Sliedrecht <[email protected]>
Signed-off-by: Berend Sliedrecht <[email protected]>
Decision made on WG call: Use @blu3beri could you make the required changes? |
Signed-off-by: Berend Sliedrecht <[email protected]>
credentialDefinitionId: credDefId, | ||
}, | ||
}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit confusing that we have metadata.data.indyRequestMetadata
? Wouldn't be more straightforward to have metadata.indyRequest
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The data object is there just for structure. My design was that the user could not easily call metadata.indyRequestMetdata
and do it through the get
,set
, etc. Now, the data
does not remove this, but I do think it promotes the usage of the functions to get values.
renaming indyRequestMetdata
to indyRequest
is okay with me. It does seem a bit too verbose now that I think about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I think it would be more concise to use just indyRequest
instead of indyRequestMetdata
at least.
What if a developer creates some metadata: credentialRecord.metadata.set('something', { a: 1 })
credentialRecord.metadata.set('something else', { b: 2 }) And then decides to remove all of them: Object.keys(credentialRecord.metadata.data).forEach(key => credentialRecord.metadata.delete(key)) Is it possible to do so? Would that delete also internal metadata? |
Yes that is possible and it would also delete the internal metadata. |
Signed-off-by: Berend Sliedrecht <[email protected]>
Signed-off-by: Berend Sliedrecht <[email protected]>
If I understand correctly, deleting the internal metadata would break the agent, right? It would be great if we're able to prevent it somehow. |
I'd say we prefix it with |
I agree with @TimoGlastra here. It will always be possible for a framework consumer to modify the data in a record. If we would, for instance, throw an error when we try to delete a key-value via I completely understand your point, but next to maybe prefixing it, which I am not even sure we should do, but I don't think we should/can do anything about this. |
That's not so easy to do. I realized it's therefore not so easy to manipulate with a metadata object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can merge it, but I still have concerns that mixing custom and framework metadata can cause trouble in the future.
If you could create an issue for it I will think about the options that are available. |
@jakubkoci What would your ideal solution be for this? Add two types of metadata to each record (framework and consumer metadata)? |
@blu3beri I think it would be good to prefix internal metadata to avoid collisions. We can then say that you are never allowed to use this prefix yourself. I've seen this pattern used in other libraries. Could you make a follow-up PR @blu3beri (rather quickly cause it's breaking again) to add the prefix? I'd say we prefix it with something like |
What would you say about an metadata.set('internal', {foo: 'bar'...}) This way we group ALL of the internal stuff in one object. (This might be considered a downside, that's why I am asking) :) |
Yes that could work, but thinking about v2 protocols supporting multiple credential types I think it may be good to e.g. split it metadata per credential type. But that could of course also be nested under the same key. It would however make updating harder (you can't use .add()`) |
Okay I 10000% agree with the fact that it is harder to update. That was the main reason I did not propose this solution earlier. I will create a new pr with the prefix. I think that |
BREAKING CHANGE:
credentialRecord.credentialMetadata
has been replaced bycredentialRecord.metadata
.Maintainers: Before merging and squashing make sure to copy the
BREAKING CHANGE
entry to the commit message field, this is needed for automatic tracking of breaking changes