Skip to content
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: extend custom ORD content #63

Merged
merged 25 commits into from
Oct 9, 2024
Merged

Conversation

zongqichen
Copy link
Contributor

No description provided.

@zongqichen zongqichen linked an issue Sep 26, 2024 that may be closed by this pull request
@swennemers
Copy link
Contributor

I was wondering, if we should have the custom ORD content be in sync with the ideas of a delta ORD document...
/open-resource-discovery-specification/pull/862/
@Fannon , what do you think?

@Fannon
Copy link
Contributor

Fannon commented Oct 2, 2024

I was wondering, if we should have the custom ORD content be in sync with the ideas of a delta ORD document... /open-resource-discovery-specification/pull/862/ @Fannon , what do you think?

Do you mean that we should not just have a way to add / completely override, but use the same idea of the "JSON Patch" format (with explicit null semantics) from https://github.tools.sap/CentralEngineering/open-resource-discovery-specification/pull/862?

I'm not entirely sure. Merging sounds better first, but it would make it really difficult to completely override something that the CAP framework generated by default. If you just want to selectively override something, you would likely use the local annotations where they belong (Service level).
We would also have to explain the merge behavior, allthough it's not that complicated (JSON Merge Patch essentially).

So we need to pick between:

Merge Behavior

  • If custom ORD content doesn't exist yet, just add it
  • If custom ORD content has same ORD ID as existing content, merge it with the CAP generated content. The values in the override take precedence. If a value in override is set to null the property is to be removed entirely.

Override Behavior

  • If custom ORD content doesn't exist yet, just add it
  • If custom ORD content has same ORD ID as existing content, replace the generated content entirely with the override.

@swennemers
Copy link
Contributor

I am not on the latest stage with the exact delta ORD syntax.

What we observe here is that we need deep merge logic (e.g. overwrite an APIs package assignment without the need for rewriting everything). Don't we need as similar logic also for delta ORD, like overwriting just the endpoint of an API?

I agree, that we need to explain the merge & overwrite logic. However, I thought, if we can sync these, we only need to describe it once

@Fannon
Copy link
Contributor

Fannon commented Oct 2, 2024

I am not on the latest stage with the exact delta ORD syntax.

What we observe here is that we need deep merge logic (e.g. overwrite an APIs package assignment without the need for rewriting everything). Don't we need as similar logic also for delta ORD, like overwriting just the endpoint of an API?

I agree, that we need to explain the merge & overwrite logic. However, I thought, if we can sync these, we only need to describe it once

Ok, but overwriting a package assignment should not be done on the global ORD override level. You should do it on the CDS Service Level with @ORD.Extensions. Here we already have a merge logic, allthough not the best one (See issue #49)

@swennemers
Copy link
Contributor

I am not on the latest stage with the exact delta ORD syntax.
What we observe here is that we need deep merge logic (e.g. overwrite an APIs package assignment without the need for rewriting everything). Don't we need as similar logic also for delta ORD, like overwriting just the endpoint of an API?
I agree, that we need to explain the merge & overwrite logic. However, I thought, if we can sync these, we only need to describe it once

Ok, but overwriting a package assignment should not be done on the global ORD override level. You should do it on the CDS Service Level with @ORD.Extensions. Here we already have a merge logic, although not the best one (See issue #49)

true, bad example with package assignment. but not every ORD Resource has the CDS entities, e.g. package. what should we do there.

@swennemers
Copy link
Contributor

Let's handle the custom package assignment in #64

@zongqichen
Copy link
Contributor Author

I was wondering, if we should have the custom ORD content be in sync with the ideas of a delta ORD document... /open-resource-discovery-specification/pull/862/ @Fannon , what do you think?

Do you mean that we should not just have a way to add / completely override, but use the same idea of the "JSON Patch" format (with explicit null semantics) from https://github.tools.sap/CentralEngineering/open-resource-discovery-specification/pull/862?

I'm not entirely sure. Merging sounds better first, but it would make it really difficult to completely override something that the CAP framework generated by default. If you just want to selectively override something, you would likely use the local annotations where they belong (Service level). We would also have to explain the merge behavior, allthough it's not that complicated (JSON Merge Patch essentially).

So we need to pick between:

Merge Behavior

  • If custom ORD content doesn't exist yet, just add it
  • If custom ORD content has same ORD ID as existing content, merge it with the CAP generated content. The values in the override take precedence. If a value in override is set to null the property is to be removed entirely.

Override Behavior

  • If custom ORD content doesn't exist yet, just add it
  • If custom ORD content has same ORD ID as existing content, replace the generated content entirely with the override.

At the moment, I use lodash.merge as merge strategy: https://lodash.com/docs/4.17.15#merge. You can see the code. It will override existing item and merge new item if not existed.

Comment on lines 12 to 23
{
"apiProtocol": "odata-v5",
"description": "new AdminService",
"ordId": "sap.test.cdsrc.sample:apiResource:AdminService:v1",
"entityTypeMappings": [
{
"entityTypeTargets": [
"new.entityType.target"
]
}
]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better have a testcase, where you add a new rest API, the other API resource overrides should be handled via @ORD.Extension mechanism, see linked issue.

@Fannon
Copy link
Contributor

Fannon commented Oct 7, 2024

At the moment, I use lodash.merge as merge strategy: https://lodash.com/docs/4.17.15#merge. You can see the code. It will override existing item and merge new item if not existed.

By default the lodash merge will not have the JSON merge patch semantics with null. You need to make sure to also cover that (please also add tests for this, as it's easy to get this wrong).

Here is some JSFiddle that I wrote for showing how AsyncAPI trait inheritance could work: https://jsfiddle.net/FannonF/yLsonr57/latest/

function apply(origin, patch) {
  if (!isObject(patch)) {
    // If the patch is not an object, it replaces the origin.
    return patch;
  }

  const result = !isObject(origin) ? // Non objects are being replaced.
    {} : // Make sure we never modify the origin.
    Object.assign({}, origin);

  Object.keys(patch).forEach(key => {
    const patchVal = patch[key];
    if (patchVal === null) {
      delete result[key];
    } else {
      result[key] = apply(result[key], patchVal);
    }
  });
  return result;
}

@zongqichen
Copy link
Contributor Author

zongqichen commented Oct 7, 2024

At the moment, I use lodash.merge as merge strategy: https://lodash.com/docs/4.17.15#merge. You can see the code. It will override existing item and merge new item if not existed.

By default the lodash merge will not have the JSON merge patch semantics with null. You need to make sure to also cover that (please also add tests for this, as it's easy to get this wrong).

Here is some JSFiddle that I wrote for showing how AsyncAPI trait inheritance could work: https://jsfiddle.net/FannonF/yLsonr57/latest/

function apply(origin, patch) {
  if (!isObject(patch)) {
    // If the patch is not an object, it replaces the origin.
    return patch;
  }

  const result = !isObject(origin) ? // Non objects are being replaced.
    {} : // Make sure we never modify the origin.
    Object.assign({}, origin);

  Object.keys(patch).forEach(key => {
    const patchVal = patch[key];
    if (patchVal === null) {
      delete result[key];
    } else {
      result[key] = apply(result[key], patchVal);
    }
  });
  return result;
}

Hi @Fannon, I have some corner cases need to discuss:

  1. if set null in array, should I remove the whole array property? e.g:
{
    "apiResources": [
        {
            "ordId": "sap.sm:apiResource:SupplierService:v1",
            "partOfGroups": [
                null,
                "sap.cds:service:sap.test.cdsrc.sample:AdminService"
            ],
            "partOfPackage": "sap.sm:package:smDataProducts:v1"
        }
    ]
}

I should remove the ´partOfGroups´?

  1. if null set in default property array, should I leave it as empty or removed?
{
    "apiResources": [
        {null},
        {
            "ordId": "sap.sm:apiResource:SupplierService:v1",
            "partOfGroups": [
                "sap.cds:service:sap.test.cdsrc.sample:AdminService"
            ],
            "partOfPackage": "sap.sm:package:smDataProducts:v1"
        }
    ]
}

or this case:

{
    "apiResources": [
         null
    ]
}

@Fannon
Copy link
Contributor

Fannon commented Oct 7, 2024

Nice summary of corner-cases, @zongqichen . I'll answer inline below:

Hi @Fannon, I have some corner cases need to discuss:

  1. if set null in array, should I remove the whole array property? e.g:
{
    "apiResources": [
        {
            "ordId": "sap.sm:apiResource:SupplierService:v1",
            "partOfGroups": [
                null,
                "sap.cds:service:sap.test.cdsrc.sample:AdminService"
            ],
            "partOfPackage": "sap.sm:package:smDataProducts:v1"
        }
    ]
}

To keep it simple (and stick with JSON Merge patch), we should always override arrays completely. Everything else will get complicated very quickly when ordering changes. Setting null inside an array is therefore invalid, as it would result in having a null entry in an array, which is not allowed in ORD.

I should remove the ´partOfGroups´?

  1. if null set in default property array, should I leave it as empty or removed?
{
    "apiResources": [
        {null},
        {
            "ordId": "sap.sm:apiResource:SupplierService:v1",
            "partOfGroups": [
                "sap.cds:service:sap.test.cdsrc.sample:AdminService"
            ],
            "partOfPackage": "sap.sm:package:smDataProducts:v1"
        }
    ]
}

This is not even valid JSON and therefore cannot be supported :)

or this case:

{
    "apiResources": [
         null
    ]
}

Same situation as with first edge-case. We'll not support it. If you want to remove the array, you need to set "apiResources": null.

@zongqichen
Copy link
Contributor Author

Nice summary of corner-cases, @zongqichen . I'll answer inline below:

Hi @Fannon, I have some corner cases need to discuss:

  1. if set null in array, should I remove the whole array property? e.g:
{
    "apiResources": [
        {
            "ordId": "sap.sm:apiResource:SupplierService:v1",
            "partOfGroups": [
                null,
                "sap.cds:service:sap.test.cdsrc.sample:AdminService"
            ],
            "partOfPackage": "sap.sm:package:smDataProducts:v1"
        }
    ]
}

To keep it simple (and stick with JSON Merge patch), we should always override arrays completely. Everything else will get complicated very quickly when ordering changes. Setting null inside an array is therefore invalid, as it would result in having a null entry in an array, which is not allowed in ORD.

I should remove the ´partOfGroups´?

  1. if null set in default property array, should I leave it as empty or removed?
{
    "apiResources": [
        {null},
        {
            "ordId": "sap.sm:apiResource:SupplierService:v1",
            "partOfGroups": [
                "sap.cds:service:sap.test.cdsrc.sample:AdminService"
            ],
            "partOfPackage": "sap.sm:package:smDataProducts:v1"
        }
    ]
}

This is not even valid JSON and therefore cannot be supported :)

or this case:

{
    "apiResources": [
         null
    ]
}

Same situation as with first edge-case. We'll not support it. If you want to remove the array, you need to set "apiResources": null.

I read JSON merge patch rfc7396, I think it's slightly different with our use case. We should not always override arrays completely. If users want to update package localId for example, they need to define following content, package ordId and localId they need to update:

{
    "packages": [
        {
            "ordId": "sap.sm:package:smDataProducts:v1",
            "localId": "smDataProducts"
        }
    ]
}

If we override completely, there are only two properties left. Our strategy is based on ordId, if no ordId mateched or provided, then extend it.

lib/extendOrdWithCustom.js Outdated Show resolved Hide resolved
lib/extendOrdWithCustom.js Outdated Show resolved Hide resolved
Copy link
Contributor

@swennemers swennemers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks quite good, let's check a few comments in the sync

lib/extendOrdWithCustom.js Outdated Show resolved Hide resolved
Copy link
Contributor

@aramovic79 aramovic79 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zongqichen @swennemers @Fannon
Please check the comment I left.

const clonedOrdContent = structuredClone(ordContent);
for (const key in customORDContent) {
const propertyType = typeof customORDContent[key];
if (propertyType !== 'object' && propertyType !== 'array') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding of this check(and after @zongqichen explained me) is that we want to allow "patch" of the resulting ORD document by appending only the properties that are arrays/lists. That then can cause some confusion, because one can put property "something" of type array, and the plugin will generate a non-valid ORD Document.
Yes, Metadata Validator is the one validating it and it's main purpose, but on the other side we are this way generating "on-the-fly" a non-valid ORD Document which is to my opinion wrong.
I would rather keep the check to strictly allowed fields("dataProducts", "entityTypes") or at least add some validation on the very begging of the ORD doc generation if the custom *ord.json file contains properties with correct names(dataProducts, entityTypes...). Something like validation schema for the json object. Still, if it is already agreed among three of you to check only if the property is array, I will approved the PR.
cc: @Fannon @zongqichen @swennemers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aramovic79 , these are good comments.
One of the use cases for this overwrite as well as also the @ORD.Extension is to allow a developer being ahead of the plugins Support for ORD. If we restrict the properties, we can't add things not yet available in the plug-in. But you are right, this comes with the risk of an broken ORD document. We can mitigate the risk with the validator and in case we want to experiment with new ORD features, we might even want the chance to do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I link this comment to the #65, lets fix it in the following pr.

@zongqichen zongqichen merged commit a159fee into main Oct 9, 2024
3 checks passed
@zongqichen zongqichen deleted the add_override_ord_information branch October 9, 2024 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow generic override / expand of ORD information on global level
4 participants