Netflix / Netflix/dgs-codegen

Interface code generation breaks serialization for nested objects

Open
#322 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Kotlin
Stars
217
Forks
116
PR merge metrics
No merged PRs in 30d

Description

generateInterfaces = true

with a schema type like:

extend type Query {
    """
    Returns a `ShipmentRating` resource by ID.
    """
    shipmentRating(input: ID!): ShipmentRating
    """
    Returns a list  of `ShipmentRating` resources with the given filters.
    """
    shipmentRatings(input: ShipmentRatingsInput): [ShipmentRating]
}

extend type Mutation {
    """
    Allows an API consumer to create a `shipmentRating`.
    """
    createShipmentRating(input: CreateShipmentRatingInput!): ShipmentRating
    """
    Allows an API consumer to calculate possible shipment ratings based on the organization's configured settings.
    """
    calculateShipmentRatings(input: CalculateShipmentRatingsInput!): [ShipmentRating]
}

enum ShipmentAmountType {
    BUFFER
    DISCOUNT
    FUEL_SURCHARGE
    INSURANCE
    PUBLISHED_RATE
    SURCHARGE
}

"""
A Shipment rating quote that can be displayed to
"""
type ShipmentRating implements Node {
    "ShipmentRating ID, prefixed with `shipmentRating_`"
    id: ID!
    "The quoted amount for the `ShipmentRating`"
    amount: Decimal!
    "Subtotal amounts of how the `ShipmentRating` amount was calculated"
    amountSubtotals: ShipmentRatingSubtotals!
    "The currencyCode of the amount fields."
    currencyCode: CurrencyCode!
    "When this `ShipmentRating` was created"
    createdAt: DateTime!
    "The user who created the `ShipmentRating`"
    createdBy: ID!
    "Breakdown of the details returned from the carrier."
    details: [ShipmentRatingDetail!]
    "The customer facing display name of the `ShipmentRating`."
    displayName: String!
    "The ISO-8601 timestamp of when the delivery could first be delivered"
    minTransitAt: DateTime
    "The ISO-8601 timestamp of when the delivery will be delivered"
    maxTransitAt: DateTime
    """
    For shipments that contain multiple fulfillment warehouses the multipleShipFromRatings will contain the individual `ShipmentRating` calculations.
    The aggregated totals will be reflected on the parent object. In these cases the shipFrom location will be null.
    """
    multipleShipFromRatings: [ShipmentRating]
    "The `organization` associated with the `ShipmentRating`"
    organization: ID!
    "A carrier service level code to identify how to fulfill the shipment"
    serviceLevelCode: String!
    "The `location` associated with the origin of the shipment."
    shipFrom: Location
    "The destination `location` used to generate the shipment Quote"
    shipTo: Location!
    "The Carton data included in the `ShipmentRating`"
    shipmentRatingCartons: [ShipmentRatingCarton!]!
    "The `ShippingProfile` associated with the `ShipmentRating`"
    shippingProfile: ShippingProfile!
    "When this `ShipmentRating` was most recently updated."
    updatedAt: DateTime!
    "The user who most recently updated the `ShipmentRating`."
    updatedBy: ID!
}

"""
The Carton details containing the package dimensions and items included in the `ShipmentRating`
"""
type ShipmentRatingCarton {
    "The total amount that the package is insured based in the currencyCode from the `ShipmentRating`."
    amountInsured: Decimal!
    "The Carton associated with the `ShipmentRatingCarton`."
    carton: Carton!
    "The weight the carrier is charging for this carton. This could be actual or dimensional weight of the carton."
    chargeableWeight: Decimal
    "The Dimensional weight factor used to determine the dimensionalWeight."
    dimensionalFactor: Int
    "The calculated dimensionalWeight of the carton."
    dimensionalWeight: Decimal
}

"""
A surcharge or discount breakdown provided by the carrier.
"""
type ShipmentRatingDetail {
    "Amount for each charge as defined by the `carrier`."
    amount: Decimal!
    "Unique identifier that will be tied to each fee that is charged by the carrier for the `shipmentRating`."
    carrierCode: String!
    "The type of fee that is being broken down as part of the `shipmentRating`."
    type: ShipmentAmountType!
}

"""
Subtotal amounts of how the `ShipmentRating` amount was calculated
"""
type ShipmentRatingSubtotals {
    "Amount charged for the fuel surcharge by the `carrier`."
    fuelSurcharge: Decimal
    "Cost to insure items that is charged by the `carrier`."
    insuranceCost: Decimal
    "The sum of any other surcharges that are not individually broken down by the `carrier` (residential falls into this bucked)."
    otherSurcharge: Decimal
    "Cost of shipping as defined by the `carrier`."
    shipping: Decimal!
}

"""
Input to create a non-calculated shipmentRating.
"""
input CreateShipmentRatingInput {
    amount: Decimal
    cartons: [ID!]!
    serviceLevelCode: String!
}

"""
Input to calculate a `shipmentRating` for the given Order.
"""
input CalculateShipmentRatingsInput {
    order: ID!
}

input ShipmentRatingsInput {
    countryCode: CountryCode
    organization: ID
    serviceLevel: ID
}

type Root @key(fields: "id") @extends {
    id: ID! @external
    shipmentRatings: [ShipmentRating]
}

type Location @key(fields: "id") @extends {
    id: ID! @external
}

type Carton @key(fields: "id")
@extends
{
    id: ID! @external
}

and a hibernate entity like this:

package com.zonos.shipmentrating.shipmentRating.model;

import com.zonos.shipmentratingclient.types.IShipmentRatingCarton;
import lombok.*;

import javax.persistence.Entity;
import java.math.BigDecimal;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class ShipmentRatingCartonEntity {

    private BigDecimal amountInsured;

    private CartonEntity carton;

    private BigDecimal chargeableWeight;

    private Integer dimensionalFactor;

    private BigDecimal dimensionalWeight;


}
 
fails to serialize with a DgsQueryExecutor call like this:


   @Test
    void getShipmentRatingTest() throws InvocationTargetException, IllegalAccessException {
        // test retrieving a shipment rating by ID
        ShipmentRating shipmentRating = createShipmentRating();

        var projection = new ShipmentRatingProjectionRoot();
        projection.id()
                .amount()
                .serviceLevelCode()
                .shipmentRatingCartons()
                .amountInsured();

        GraphQLQueryRequest request = new GraphQLQueryRequest(
                new ShipmentRatingGraphQLQuery.Builder()
                        .input(shipmentRating.getId())
                        .build(),
                projection,
                getZonosScalars()
        );
        ShipmentRating retrievedShipmentRating = dgsQueryExecutor.executeAndExtractJsonPathAsObject(request.serialize(),
                "data." + new ShipmentRatingGraphQLQuery().getOperationName(),
new TypeRef<List<ItemEntity>>() {
              });
        assertNotNull(retrievedShipmentRating);
        assertTrue(retrievedShipmentRating.getId().equals(shipmentRating.getId()));
        assertTrue(retrievedShipmentRating.getShipmentRatingCartons().size() == 1);
    }


Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the failure in the DgsQueryExecutor test shown in the issue, using the ShipmentRating schema and ShipmentRatingCartonEntity. Trace the generated interface and nested ShipmentRatingCarton serialization for the amountInsured projection. Done means the nested object serializes successfully and the test can extract the shipment rating with its carton data.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, java, kotlin
Domain
api, backend, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.