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

Optional links support #153

Merged
merged 5 commits into from
May 30, 2017

Conversation

bjornharrtell
Copy link
Contributor

Aims to resolve #150.

Not yet finished though but wanted to ask about the approach. I couldn't see it possible to handle this in the UrlPathBuilder but had to involve the ResourceSerializer. Also, I'm not sure about what a "canonical" url is and so have problems understanding the purpose of Related vs RelatedCanonical.

Copy link
Owner

@joukevandermaas joukevandermaas left a comment

Choose a reason for hiding this comment

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

I think this approach makes sense. I originally thought the UrlPathBuilder could return null based on the resource, then the serializer could omit the links hash if empty. I think your approach is more clear.

I do think we need some more tests, at least one for each of the enum values.

@@ -58,6 +68,8 @@ protected ApiResource()
/// </summary>
public string IdProperty { get; private set; }

public LinkTypes WithLinks { get; set; } = LinkTypes.All;
Copy link
Owner

Choose a reason for hiding this comment

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

I don't think ApiResource should have any mutable properties.

var result = new JObject
var result = new JObject();

if (_resource.WithLinks.HasFlag(LinkTypes.Self & LinkTypes.All))
Copy link
Owner

Choose a reason for hiding this comment

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

I think you can just do HasFlag(LinkTypes.Self), since All includes that. Now this is only added when the value is LinkTypes.All.


item["links"] = links;
if (!string.IsNullOrEmpty(self) && relationship.WithLinks.HasFlag(LinkTypes.Related & LinkTypes.All))
Copy link
Owner

Choose a reason for hiding this comment

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

Same here, you don't need to do the &.

AddUrl(links, "self", self);
}

if (!string.IsNullOrEmpty(related) && relationship.WithLinks.HasFlag(LinkTypes.Related & LinkTypes.All))
Copy link
Owner

Choose a reason for hiding this comment

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

And here.

@bjornharrtell
Copy link
Contributor Author

Thanks @joukevandermaas, good to know I'm hopefully on the right path. Will ping you when I have had the time to make this PR complete and properly reviewable and will take your early comments into consideration.

@bjornharrtell bjornharrtell changed the title WIP Optional links support Optional links support May 18, 2017
@bjornharrtell
Copy link
Contributor Author

bjornharrtell commented May 18, 2017

Improved now but amount of tests still too few and should try it out on a real API implementation too.

@bjornharrtell
Copy link
Contributor Author

Additional tests added and seems to do what I expect in a real project. Ready for review now @joukevandermaas.

But... I did notice a possible regression with the new serializer. Using 1.7.0.390 I'm missing the data property for relationships (only tested BelongsTo as of yet), downgrading to stable 1.6 resolves it.

@laurence79
Copy link
Contributor

@bjornharrtell damn... could you give me an example or even better, a test, and I'll investigate the regression?

@bjornharrtell
Copy link
Contributor Author

@laurence79 will try to produce a unit test to demonstrate the issue.

@bjornharrtell
Copy link
Contributor Author

See testcase in #155.

Copy link
Owner

@joukevandermaas joukevandermaas left a comment

Choose a reason for hiding this comment

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

I think there's a slight confusion because both resources and relationships can define links.

This also needs documentation because of new public API.


item["links"] = links;
if (!string.IsNullOrEmpty(self) && relationship.LinkType.HasFlag(LinkType.Self))
Copy link
Owner

Choose a reason for hiding this comment

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

I think for resources, we should both check their resources LinkType as well as the relationship's LinkType. It should probably resolve to the most restricting value.

Exampe:

PersonResource: WithLinks(All)
  - BelongsTo<CompanyResource> Job, WithLinks(None)
CompanyResource: WithLinks(Self)

Now when serializing the Job relationship, what links should show up? If I read this code correctly, currently it will serialize the self link even if the BelongsTo specified not to.

We probably also need a test case for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question, I have a hard time getting a grip on what would be correct/desired behaviour here. Will have to think about it some more.

Copy link
Owner

Choose a reason for hiding this comment

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

I wrote down some thoughts on this below. Please let me know what you think.

It's most likely that one of the two will just be All. I expect the most common scenario is to just override something in one place (i.e. either the relationship or on the model, depending which best maps to the task.

This implies to me that we should take the most restrictive of the options. Examples:

  • Person -> Company (Job relationship). Person and company both have link type All, the relationship has link type Related. When serializing individual person or company records, we should serialize all links. When serializing the job relationship, we should serialize only related links.
  • Person -> Company (Job relationship). Person has link type All, company has Related. The relationship has link type All. When serializing individual person records, we should serialize all links. For company records, we should not serialize anything (because there's no related links in that case). For the job relationship, we should serialize only related links.

One case where I'm not sure:

  • Person -> Company (Job relationship). Person and company both have link type None. The relationship has link type Related. When serializing individual records, we should serialize no links. For the job relationship, what should we do? It sort of makes sense to serialize no links, but then again, the user explicitly set the link type to Related so they probably want those.

I guess the solution would be something like this:

  1. When not serializing relationships, there is only one option so this should be simple.
  2. Otherwise, if one of the options is All, take the other option.
  3. In all other cases, use the logical 'or' of the options.

Examples of this:

  • All and None -> serialize none
  • All and Related -> serialize related
  • Related and None -> serialize none
  • Related and Self -> serialize related & self only (where they make sense)

Of course, we always have the option to just ignore one of the two:

  1. When serializing the related hash, use the relationship link type
  2. When serializing models (i.e. the top level data or included hash), use the resource link type

This is simple (I believe this is what the code in this PR does now) and seems the easiest to document.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm leaning on keeping it simple. The more complex path still hurts my brain. If one has other needs it could be implemented as a custom UrlBuilder. That said I might be shooting myself in the foot here. What is your opinion?

Copy link
Owner

Choose a reason for hiding this comment

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

Yeah, I tend to agree. We can always expand on it later.

&& kv.Value.SourceObject != null)
{
relationshipId = (string)data["id"];
}

var links = new JObject();
AddUrl(links, "self", _urlBuilder.BuildRelationshipPath(node.Resource, node.Key.Id.ToString(), kv.Value.Relationship));
AddUrl(links, "related", _urlBuilder.BuildRelationshipPath(node.Resource, node.Key.Id.ToString(), kv.Value.Relationship, relationshipId));
var self = _urlBuilder.BuildRelationshipPath(node.Resource, node.Key.Id.ToString(), relationship);
Copy link
Owner

Choose a reason for hiding this comment

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

We don't need to generate these if we might not use them, I think they should go inside the ifs below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed because I check for empty link generation. I.e unrelated to the LinkType configuration I want to avoid generating the links if the UrlBuilder returns null which I think is in line what you once wrote in #121?

@joukevandermaas
Copy link
Owner

I like the changes! I think this can be merged once the documentation has been updated (I think docs/generating-links.md is a good spot).

@bjornharrtell
Copy link
Contributor Author

Great, will add docs/generating-links.md asap. But what about RelatedCanonical? Shall I remove that one?

@bjornharrtell
Copy link
Contributor Author

Added documentation and tentatively removed RelatedCanonical enum type.

Copy link
Owner

@joukevandermaas joukevandermaas left a comment

Choose a reason for hiding this comment

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

The markdown parser used by GH pages is different, and IIRC there needs to be a new line before code blocks.

```

When defining a `Relationship`:
```csharp
Copy link
Owner

Choose a reason for hiding this comment

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

I think there needs to be a new line before a code block or the parser messes up the layout.

specific `ApiResource` or `Relationship` by setting it as in the following examples:

In your `ApiResource` constructor:
```csharp
Copy link
Owner

Choose a reason for hiding this comment

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

I think there needs to be a new line before a code block or the parser messes up the layout.

@joukevandermaas
Copy link
Owner

Thanks for seeing this through! It turned out really nice, I think.

@joukevandermaas joukevandermaas merged commit bba318e into joukevandermaas:master May 30, 2017
@bjornharrtell bjornharrtell deleted the optional-links branch May 30, 2017 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make links optional
3 participants