Generate links for collection resources for a specific single resource - spring-data-rest

I wrote a custom controller to handle a GET http://localhost:54000/api/v1/portfolios/{id}/evaluate request.
#RequestMapping(value = "/portfolios/{id}/evaluate", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> evaluate(#PathVariable Long id) {
Portfolio portfolio = portfolioService.evaluate(id);
if (portfolio == null) {
return ResponseEntity.notFound().build();
}
Resource<Portfolio> resource = new Resource<>(portfolio);
resource.add(entityLinks.linkForSingleResource(Portfolio.class, id).withSelfRel());
return ResponseEntity.ok(resource);
}
The current response is
{
"summary" : {
"count" : 24.166666666666668,
"yield" : 0.14921630094043895,
"minBankroll" : -6.090909090909091,
"sharpeRatio" : 0.7120933654645042,
"worstReturn" : -2.4545454545454533,
"losingSeason" : 3,
"return" : 3.6060606060606077
},
"_links" : {
"self" : {
"href" : "http://localhost:54000/api/v1/portfolios/4"
}
}
}
but I would like to add collection resources (summaries and systems) linked to that portfolio:
{
"summary": {
"count": 24.166666666666668,
"yield": 0.14921630094043895,
"minBankroll": -6.090909090909091,
"sharpeRatio": 0.7120933654645042,
"worstReturn": -2.4545454545454533,
"losingSeason": 3,
"return": 3.6060606060606077
},
"_links": {
"self": {
"href": "http://localhost:54000/api/v1/portfolios/4"
},
"portfolio": {
"href": "http://localhost:54000/api/v1/portfolios/4"
},
"summaries": {
"href": "http://localhost:54000/api/v1/portfolios/4/summaries"
},
"systems": {
"href": "http://localhost:54000/api/v1/portfolios/4/systems"
}
}
}
I did not find a way to generate those links with the RepositoryEntityLinks entityLinks object

You can always do something like this:
entityLinks.linkForSingleResource(Portfolio.class, id).slash("systems").withRel("systems");
And if your systems endpoint is implemented in a custom controller method you can use the ControllerLinkBuilder to generate a link to your controller method. Lets say you implemented the getSystems method with id parameter in MyControllerClass - then you can generate the link like this (linkTo and methodOn are static methods in ControllerLinkBuilder):
linkTo(methodOn(MyControllerClass.class).getSystems(id)).withRel("systems");

Related

GraphQL pagination partial response with error array

I have a query like below
query {
heroes {
node {
name
}
endCursor
}
}
I am trying to understand how GraphQL can handle the error handling and return partial response. I looked at https://github.com/graphql/dataloader/issues/169 and tried to create a resolver like below;
{
Query: {
heroes: async (_) => {
const heroesData = await loadHeroesFromDataWarehouse();
return {
endCursor: heroesData.endCursor;
node: heroesData.map(h => h.name === 'hulk' ? new ApolloError('Hulk is too powerful') : h)
}
}
}
}
I was hoping it would resolve something like below;
{
"errors": [
{
"message": "Hulk is too powerful",
"path": [
"heroes", "1"
],
}
],
"data": {
"heroes": [
{
"name": "spiderman"
},
null,
{
"name": "ironman"
}
]
}
}
but it is completely failing making the heroes itself null like below;
{
"errors": [
{
"message": "Hulk is too powerful",
"path": [
"heroes"
],
}
],
"data": {
"heroes": null
}
}
How can I make resolver to return me the desired partial response?
Found the solution, basically we need a resolver to resolve the edge model itself;
{
Query: {
heroes: (_) => loadHeroesFromDataWarehouse()
},
HeroesEdge {
node: async (hero) => hero.name === 'hulk' ? new ApolloError('Hulk is too powerful') : hero
}
}

Shopify Storefront API getProductMedia

I tried following the offical Shopify Documentation for retrieving ProductMedia.
My Query looks like this:
query getProductMediaById($id: ID!) {
product(id: $id) {
id
media(first: 10) {
edges {
node {
mediaContentType
alt
...mediaFieldsByType
}
}
}
}
}
fragment mediaFieldsByType on Media {
... on ExternalVideo {
id
embeddedUrl
}
... on MediaImage {
image {
...imageAttributes
}
}
... on Model3d {
sources {
url
mimeType
format
filesize
}
}
... on Video {
sources {
url
mimeType
format
height
width
}
}
}
fragment imageAttributes on Image {
altText
url
}
The only thing where I diverged from the official documentation is to put the image attributes to another fragment for code reuse.
But when I try to execute the query I get the following response:
{
"data": {
"product": {
"__typename": "Product",
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzY3NjcyOTczMzEzMDU=",
"media": {
"__typename": "MediaConnection",
"edges": [
{
"__typename": "MediaEdge",
"node": {
"__typename": "MediaImage",
"mediaContentType": "IMAGE",
"alt": ""
}
}
]
}
}
},
"loading": false,
"networkStatus": 7
}
Or to put it to words my response doesn't contain any information from the mediaFieldsByType fragment.
Any Idea what I'm doing wrong?

Use a projection to modify only one field but keeping default for all others

First of all I'd like to say I love what i've seen so far from Spring Data JPA and Spring Data REST. Thanks a lot to all people involved.
Problem description
I have an entity model similar to the classes below. One parent and two different child entities referencing the parent als a ManyToOne Assoziation. For one of the childs i like to have the default rendering of all its properites and links as it is when no projection is applied to the parent.
The other child should be mapped to a simple string array containing only the id or some specific field.
Code and example JSONs
#Entity
public class Parent {
#Id
private Long id;
private String parentValue;
#OneToMany(mappedBy = "parent")
private List<Child1> child1;
#OneToMany(mappedBy = "parent")
private List<Child2> child2;
// ... getters and setters
}
#Entity
public class Child1 {
#Id
private Long id;
private String child1Value;
#ManyToOne
Parent parent;
// ... getters and setters
}
#Entity
public class Child2 {
#Id
private Long id;
#ManyToOne
Parent parent;
}
the response when getting the collection resource of parent is this
{
"_embedded": {
"parents": [
{
"parentValue": "Parent1",
"child1": [
{
"child1Value": "Child1",
"_links": {
"parent": {
"href": "http://localhost:8080/parents/1"
}
}
}
],
"child2": [
{
"_links": {
"parent": {
"href": "http://localhost:8080/parents/1"
}
}
}
],
// removed remaining json to shorten the example
}
But what i like to achieve is the following JSON
{
"_embedded": {
"parents": [
{
"parentValue": "Parent1",
"child1": [
{
"child1Value": "Child1",
"_links": {
"parent": {
"href": "http://localhost:8080/parents/1"
}
}
}
],
"child2": [1],
What i tried so far
Added an excerptProjection to the ParentRepository:
#RepositoryRestResource(excerptProjection = ParentRepository.ArrayProjection.class)
public interface ParentRepository extends PagingAndSortingRepository<Parent, Long>{
public interface ArrayProjection {
String getParentValue();
List<Child1> getChild1();
#Value("#{target.child2.![id]}")
List<Long> getChild2();
}
}
Edited: In the first version of the question, the Projection was incorrect regarding the return type of getChild1(), as it should return the complete collection not only one element. Thanks #kevvvvyp for still trying to help.
The result is similar to what i want, but the links on the Child1 property are missing now:
{
"_embedded": {
"parents": [
{
"child2": [
2
],
"child1": {
"child1Value": "Child1"
},
"parentValue": "Parent1",
// removed remaining json to shorten example
Also the approach with the excerptProjection means i'd have to change the projection everytime the entity changes. Which probalby won't happen to much but that means somebody will forget to change it in the future ;-)
I think a projection is the right way to go here, what about...
interface ArrayProjection {
#JsonIgnore
#Value("#{target}")
Parent getParent();
default String getParentValue(){
return this.getParent().getParentValue();
}
default Child1 getChild1(){
//TODO what are we actually trying to return here? Given we join the parent to
// the child on id & id is the primary key on both entities can we ever get more than one child?
return CollectionUtils.firstElement(this.getParent().getChild1());
}
default List<Long> getChild2() {
return this.getParent().getChild2().stream()
.map(Child2::getId)
.collect(Collectors.toList());
}
}
Response...
GET http://localhost:8080/api/parents
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 31 Mar 2021 21:08:54 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"_embedded": {
"parents": [
{
"parentValue": "Parent1",
"child1": {
"child1Value": "Child1"
},
"child2": [
1
],
"_links": {
"self": {
"href": "http://localhost:8080/api/parents/1"
},
"parent": {
"href": "http://localhost:8080/api/parents/1{?projection}",
"templated": true
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/parents"
},
"profile": {
"href": "http://localhost:8080/api/profile/parents"
}
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
Response code: 200; Time: 713ms; Content length: 724 bytes
In reponse to your concern, if we code using default method's we will see a compile error when that entity changes. It might be possible to use a class projection instead.
Also I would consider if we really want to return a projection by default... it might confuse a client who then tries to create/update a parent (this is perhaps why you've kept the id's hidden by default?).
Sample - https://github.com/kevvvvyp/sdr-sample
Update
Another (more complex) way to acheive this could be to make use of Jackson's SimpleBeanPropertyFilter, you could customise the JSON response based on some value (the entity class, a value in a particular field, an annotation etc).

Jackson module to handle abstract aggregate root and its subclasses in Spring Data REST

I have Spring Data REST based application with repository
public interface CriterionRepository extends JpaRepository<Criterion, Long> {
}
whereas Criterion is base class:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Criterion extends AbstractEntity {}
and NameCriterion is its subclass
#Entity
public class NameCriterion extends Criterion {
private final String name;
}
Spring Data REST exports the repository as REST resource and one can access it at http://localhost:8080/api/criteria/
Exported resource looks as follows:
{
"_embedded": {
"nameCriteria": [{
"_links": {
"self": {
"href": "http://localhost:8080/api/nameCriterion/1"
},
"nameCriterion": {
"href": "http://localhost:8080/api/nameCriterion/1"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria"
},
"profile": {
"href": "http://localhost:8080/api/profile/criteria"
}
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
When I try to follow self link, there is no mapping for http://localhost:8080/api/nameCriterion/1
I can follow http://localhost:8080/api/criteria/1 though and I get response without name field from NameCriterion
{
"_links": {
"self": {
"href": "http://localhost:8080/api/nameCriterion/1"
},
"nameCriterion": {
"href": "http://localhost:8080/api/nameCriterion/1"
}
}
}
My assumption is it is a problem with Jackson mapper defined in REST exporter which is not tweaked correctly to handle abstract class Criterion used in JpaRepository as aggregate root.
What Jackson customization should I apply to make it working properly?
In other words, what Jackson module should I create?
There is no need to create a Jackson module. To use a single table for inherited entities we can use #RestResource annotation to mark them as the same resources:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "criteria")
public abstract class Criterion extends AbstractEntity {
}
#RestResource(rel = "criteria", path = "criteria")
#Entity
public class NameCriterion extends Criterion {
private String name;
}
#RestResource(rel = "criteria", path = "criteria")
#Entity
public class TitleCriterion extends Criterion {
private String title;
}
#RepositoryRestResource(path = "criteria", collectionResourceRel = "criteria", itemResourceRel = "criterion")
public interface CriterionRepository extends JpaRepository<Criterion, Long> {
}
So it becomes possible to obtain all the resources (NameCriterion and TitleCriterion) in one output:
GET http://localhost:8080/api/criteria
{
"_embedded": {
"criteria": [
{
"name": "name1",
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria/1"
},
"nameCriterion": {
"href": "http://localhost:8080/api/criteria/1"
}
}
},
{
"title": "title1",
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria/2"
},
"titleCriterion": {
"href": "http://localhost:8080/api/criteria/2"
}
}
}
]
}
}
GET http://localhost:8080/api/criteria/1
{
"name": "name1",
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria/1"
},
"nameCriterion": {
"href": "http://localhost:8080/api/criteria/1"
}
}
}
GET http://localhost:8080/api/criteria/2
{
"title": "title1",
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria/2"
},
"titleCriterion": {
"href": "http://localhost:8080/api/criteria/2"
}
}
}
Working example.

Using .Net Core Web API with JsonPatchDocument

I am using JsonPatchDocument to update my entities, this works well if the JSON looks like the following
[
{ "op": "replace", "path": "/leadStatus", "value": "2" },
]
When i create the object it converts it with the Operations node
var patchDoc = new JsonPatchDocument<LeadTransDetail>();
patchDoc.Replace("leadStatus", statusId);
{
"Operations": [
{
"value": 2,
"path": "/leadStatus",
"op": "replace",
"from": "string"
}
]
}
if the JSON object looks like that the Patch does not work. I believe that i need to convert it using
public static void ConfigureApis(HttpConfiguration config)
{
config.Formatters.Add(new JsonPatchFormatter());
}
And that should sort it out, the problem is i am using .net core so not 100% sure where to add the JsonPatchFormatter
I created the following sample controller using the version 1.0 of ASP.NET Core. If I send your JSON-Patch-Request
[
{ "op": "replace", "path": "/leadStatus", "value": "2" },
]
then after calling ApplyTo the property leadStatus will be changed. No need to configure JsonPatchFormatter. A good blog post by Ben Foster helped me a lot in gaining a more solid understanding - http://benfoster.io/blog/aspnet-core-json-patch-partial-api-updates
public class PatchController : Controller
{
[HttpPatch]
public IActionResult Patch([FromBody] JsonPatchDocument<LeadTransDetail> patchDocument)
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var leadTransDetail = new LeadTransDetail
{
LeadStatus = 5
};
patchDocument.ApplyTo(leadTransDetail, ModelState);
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
return Ok(leadTransDetail);
}
}
public class LeadTransDetail
{
public int LeadStatus { get; set; }
}
Hope this helps.