4.4.2 • Published 15 days ago

connect-carrier-api-utils v4.4.2

Weekly downloads
-
License
ISC
Repository
-
Last release
15 days ago

Connect carrier-api utils

Connect carrier-api utils is a package that support a carrier integration process on SE Connect platform.

Keep in mind it is not officially supporting package by ShipStation or ShipEngine organization. Due to this fact, we are not responsible for any issues related to this package.

Implementation based on SE-connect models and provide unified and easy to use:

  • validation rules,
  • third party connector implementation,
  • default implementations,
  • SE logic helpers,
  • generic helpers, strings, parsers, etc.,
  • fake tests object builders and addresses ready to use,
  • implementation for connecting with rendering service to creating labels.

How to start

Only what you need is just run npm i @shipengine/connect-carrier-api-utils command inside your project directory.

How to use a validation helper

The validation process include 3 steps. 1. Make a reference to services definition data. Define when the service is selected if there should be custom logic behind that. 2. Connect services with validations. Determinate validations for each service. 3. Execute validations.

At the beginning you should be familiar with following objects: ServiceDetails, ValidationPlan, ValidatonExecutor and validations rules.

Quick overview

Instance of ValidationExecutor based on the ValidationPlan to execute validations and return a validation Result object or throw error in case of validation error.

The ValidationPlan is a pair of the specific ServiceDetails object and validation list related to them.

Step 1. Define services

To give a module more information about services you can create a new class that decorate a ShippingService definitions. This class should inherit from the ServiceDetails - it is an abstract class which contains logic to check if service is selected.

Using the ShippingService as a data source allows to use in module exactly the same value, which is provided in definition. E.g you can keep additional Code property.

class StandardService extends ServiceDetails {
    SERVICE_IDENTIFIER = "Standard";

    constructor(shippingService: ShippingService) {
        super(shippingService);
    }

    readonly Code: string = this.shippingService.Code;
}

As a constructor parameter you should use ShippingService instance, which is implementation for ShipStation definitions.

export const SaturdayService: ShippingService = {
    Id: "3448ebe0-e70d-48ec-8ec2-5dc813e57888",
    Name: "Saturday",
    Code: "saturday_delivery",
    International: false,
    Abbreviation: "saturdayService"
};

The base method from a ServiceDetails delivery isServiceSelected method which is used to determine if the service is selected by validation logic and name property which is using to determinate that.

export abstract class ServiceDetails {
    protected abstract SERVICE_IDENTIFIER: string;

    isServiceSelected(serviceIdentifier: string): boolean {
        return serviceIdentifier === this.shippingService.Name;
    }

    constructor(protected shippingService: ShippingService) { }
}

You can easily override this method to change logic that checks if service is selected.

class StandardService extends ServiceDetails {
    SERVICE_IDENTIFIER = "Standard";

    constructor(shippingService: ShippingService) {
        super(shippingService);
    }

    override isServiceSelected(serviceIdentifier: string): boolean{
        return serviceIdentifier === this.shippingService.Code;
    }
}

Step 2. How to configure validation plan executor

Plan executor is a class that is responsible for executing provided validation rules.

To configure validation plan executor you can use the following code:

    const validationPlan = new ValidationPlan(
        new Map(
            [
                [new ServiceDetails(StandardService), [
                    new FakeFirstValidation(),
                    new FakeSecondValidation(),
                    new validateParcelWeight(1, 2000)]]
            ]));

The Validation plan contains map of the ServiceDetails and the validations. You can add new validations to the plan in a runtime.

validationPlan.addValidation(StandardServiceInstance, new ValidateCustomsItemsRequiredIfOneSelected());

Step 3. How to execute validation plan

The ValidationExecutor is a class that is responsible for finding out what service is selected and executing provided validation rules.

To activate the validationExecutor you can use the following code:

const validationExecutor = new ValidationExecutor(validationPlan);

validationExecutor.execute(CreateLabelRequest);

After that the rules are executed in the order they are provided. As an output for this method you can receive Result object with will be handled and errors will be throw in case of validation error.

How to add custom validation

To add custom validation rule you need to implement CustomValidationRule interface and add instance to the ValidatorPlan to related service.

class CustomValidationRule implements CustomValidationRule {
    validate(request: CreateLabelRequest): Result {
        // your logic here
        return Result.Success;
    }
}

validationPlan.addValidation(StandardServiceInstance, new CustomValidationRule());

List of the validations rules

Name of the methodArgumentsDescription
CustomsItemsRequiredIfOneSelectedValidatorNoneIf any customs value is selected ensure all of them should be provided
LabelSizeAndFormatValidatorsupportedLayouts: LabelLayouts[], supportedDocuments: DocumentFormat[]If layout or documents is not supported the error will be returned.
MandatoryAddressParametersValidatoraddressType: AddressType, fieldsToValidate: AddressFieldsChose which address should be validate, and which parameter should be provided.
NumberOfPackageValidatormax: number, min: number = 0The number of the package should be in range provided in arguments list
ParcelWeightValidatorminWeight: number, maxWeight: numberWeight of the parcels should be in range provided in arguments list
CheckAllowedAddressCountryallowedCountries: string[], addressType: AddressType = AddressType.ReceiverCheck country code for provided address type is allowed for provided values list
ValidateParametersBySchemavalidateSchema: objectValidate any object by provided valid JSON schema
CheckPackagesMaxDimensionsmaxDimensions: number[]Validate all packages dimensions by provided maximum values

How to implement third party support

The solution delivered by this package is a set of helper functions that can be used to implement third party support.

To create TPS connection you need make 2 things: 1. Inheritance ThirdPartyConnector to make sure logs will contains correct module oriented prefix and refresh token logic will be implemented. 2. Make a ThirdPartyRequest instance that contains all the data you need to make a request to the third party service.

Step 1. Create ThirdPartyConnector

First of all, you need to implement the ThirdPartyConnector generic class.

export class ModuleThirdPartyConnector extends ThirdPartyConnector {

    constructor() {
        super("ModuleThirdPartyConnector");
    }
}

Value provided as a argument of the ThirdPartyConnector constructor is a identifier of the module. It is used to identify the module in the logs.

Step 2. Create ThirdPartyRequest

Next you need to create a ThirdPartyRequest instance.

The request object is a generic object that is used to send to carrier API.

To create and configure request object you can use following implementation:

    const standardRequest = new ThirdPartyRequest().Method(ThirdPartyMethods.GET).Url("https://www.google.com").Body({})

ThirdPartyRequest support following fluent configuration methods:

MethodDescription
MethodSet method of the request
UrlSet url of the request
BodySet body of the request
HeadersSet headers of the request
QuerySet query of the request

Refresh Token

In case the API required any type of authentication you can decorate your request, in a way that handle check logic and refresh logic out of the box.

In case of custom authorization you need to implement IRefreshAuthProcess interface.

MethodDescription
IsRefreshRequired()Logic that check if refresh logic is required, depends on time or something.The metadata can be pass as an constructor.
RefreshBehaviour()In this method you want to determinate how the refresh request looks like
ApplyRefreshBehaviour()In case refresh was done it might be required to override some data in base request.In this method you got as an param response form the refresh token and your main request.
UpdateMetadata()This method should override auth data in metadata.

Example of the usage:

const refreshTokenImplementation = new RefreshTokenAlwaysRequiredRefreshTestImpl();

const connector = new ThirdPartyConnectorAuthentication(baseConnector, refreshTokenImplementation, [404]);

var response = await connector.SendRequest<any, any>(new ThirdPartyRequest());

refreshTokenImplementation.UpdateMetadata(createLabelRequest.metadata);

Handle response based on status code

If you expect specific status code returned from the API in case of success or fail, then you can use SendRequestWithStatuses method on the ThirdPartyConnector object. As an argument you have to provide RequestExpectedStatuses object.

const response = await moduleThirdPartyConnectorImplementation.SendRequestWithExpectedStatuses<ResponseTestModel, ErrorTestModel>(standardRequest, new RequestExpectedStatuses([200], [404])));

Example of usage

Since the ThirdPartyConnector class is responsible for handling communication with web service, action is executed by calling send method.

    public async sendRequest<Response, ErrorResponse>(request: ThirdPartyRequestProvider):
        Promise<Response | ErrorResponse | undefined> { }

As you can see on definition Send method requires a request object. E.g

const standardRequest = new ThirdPartyRequest()
.Method(ThirdPartyMethods.GET)
.Url('http://localhost:5099/Test')
.Body({}); // create a standard request

Next you can call a method. As a generic objects, you can use any model.

const response = await moduleThirdPartyConnectorImplementation.sendRequest<ResponseTestModel, ErrorTestModel>(standardRequest)

In the example above ResponseTestModel and ErrorTestModel are declared as a expected model in order: response, error from carrier API.

Those models are defined in the module, depends on a carrier documentation. E.g:

export class ResponseTestModel {
    public barcode: string;
}

export class ErrorTestModel {
    public message: string;
}

As a result of calling send method, ThirdPartyConnector will return ResponseTestModel or ErrorTestModel object depending on TPS connection response and parsing response.

How to add custom third party request builder logic

In case you want to add custom features to request, you can decorate request like this base decorator:

    export class ThirdPartyRequestResponseTypeBase64 extends ThirdPartyRequest {

        constructor(public thirdPartyRequest: ThirdPartyRequest) {
            super();
            this.thirdPartyRequest = thirdPartyRequest;
            this.thirdPartyRequest.ThirdPartyRequestProvider.responseEncoding = 'arraybuffer';
        }
    }

Usage example:

    const standardRequest = new ThirdPartyRequest().Method(ThirdPartyMethods.GET).Url("https://www.google.com").Body({})
    const decoratedRequest = new ThirdPartyRequestResponseTypeBase64(standardRequest);

How to implement SOAP support

This package contains a base implementation of a SOAP connector that may help with creation of SOAP based carrier integrations.

Step 1

Create an instance of class ThirdPartySoapConnector and initialize it. For each SOAP service provided by the carrier API a separate instance of the ThirdPartySoapConnector should be created.

    const connector = new ThirdPartySoapConnector();
    await connector.Initialize('URL_TO_WSDL');

Step 2

Set desired SOAP headers required by the carrier API, these are often used for authentication.

    connector.SetSoapHeaders({
        'ClientId': 'test_client_id',
        'AuthenticationTicket': 'test_auth_ticket'
    });

Step 3

Sometimes it may be required to use custom namespaces in requests, in that case it's necessarry to manually add them to the xml envelope.

    connector.AddCustomNamespaceToEnvelope('dat', 'URL_TO_THE_NAMESPACE_DEFINITION');

Step 4

Define a request by providing the SOAP method it should call and the data. The ThirdPartySoapRequest class is parametrized and you need to provide the type of the data it contains, in the example it's the AuthenticateRequest type.

    const requestData: AuthenticateRequest = {
        'UserName': 'dummy_username',
        'Password': 'dummy_password'
    };

    return new ThirdPartySoapRequest<AuthenticateRequest>()
        .Method('Authenticate')
        .Data(requestData);

Step 5

Send the request. The SendRequest method is parametrized and you need to provide the type of data that will be returned from the carrier.

    const response = await connector.SendRequest<AuthenticateRequest, AuthenticateResponse>(request);

Step 6

During the module's lifetime it might be necessary to update existing SOAP headers, in that case you can use the UpdateSoapHeader method:

    await connector.UpdateSoapHeader(':AuthenticationTicket', 'new_auth_ticket');

List of helpers

MethodDescription
GetFirstNotEmptyPass the number of strings and it will return the first not empty string.
CheckAndSubstringTextCheck if the text is longer than the max length and if so, it will be truncated.
FormatUrlFormat url with query parameters.
GetSafeStringGet safe string from the string.
HandleErrorHandle error in preferred by SE Connect way.
LogInfoLog info in preferred by SE Connect way.
LogErrorLog error in preferred by SE Connect way.
GetGUIDGet GUID.
GetSafeValueFromMapGet safe value from map.
GetTrackingNumberGet tracking number.
GetValueFromMetadataGet value from metadata in a safe way.
CheckIfMandatoryParametersAreProvidedCheck list of the parameters if are provided
FormatUtcDateFormats the given date to the given format converted to UTC time zone
StringToBase64Encode string to base64.
ArrayBufferToBase64Encode provided ArrayBuffer to base64 string
GetSortedPackageDimensionsReturns sorted package dimensions in ascending or descending order
IsValueAllowedCheck if value is allowed from the provided values

List of tests fake objects

Every class contains a Build() method that return SE object. You can easy set property of the object using fluent interface methods. E.g

    const fakeObject = new FakeAddress().SetCountry().SetCity().Build();

Or

    const fakeObject = new FakeAddress().SetDefault("PL001").Build();
ClassDescription
FakeAddressFake object for address.
FakePackageFake object for package.
FakeCreateLabelRequestBuilderFake object for create label.
FakeCreateManifestRequestBuilderFake object for create manifest.
FakeCustomItemFake object for custom item.
FakeGetTrackRequestFake object for get track.
FakeRegisterRequestBuilderFake object for register.
FakeShipFromFake object for ship from.
FakeShipToFake object for ship to.

Feature flags

Feature flags can turn certain functionality on and off during runtime, without deploying new code. They are activated by Launch Darkly based on a specific Seller ID.

To get feature flags, you need to execute the following code:

const value = `GetFeatureFlag('featureFlagKey', 'sellerId');`

DynamoDB

Some carrier modules are required to store information such as routing data or PUDO points in a DynamoDB database. Class DynamoDBConnectClient provides an interface that simplifies the usage of some commonly used DynamoDB features.

Connecting to a DynamoDB database

First create an instance of DynamoDBConnectClient. If you wish to connect to our AWS hosted instance of DynamoDB, leave out the endpoint.

const dynamoDB = new DynamoDBConnectClient('table-name', 'aws-region-name (e.g eu-west-1)', 'endpoint (e.g. http://localhost:8000)');

Writing data into DynamoDB

To write data to DynamoDB use the method Write with an array of objects as the parameter. The array will be automatically split into batches of 25 items and will be saved in DynamoDB.

const exampleItems = [
    {
        pk: 1,
        name: 'name_1'
    },
    {
        pk: 2,
        name: 'name_2'
    }
];

await dynamoDB.Write(records);

Retrieving data from DynamoDB

To retreive data from DynamoDB use the method Query with suitable parameters. For more information about the parameters, see the AWS documentation. The returned data will be automatically parsed into an array of objects of the desired type.

const keyConditionExpression = 'pk = :pk';
const filterExpression = 'name = :name';
const expressionAttributeValues = {
    ':pk': 1,
    ':name': 'name_1',
};

const queryResult = await dynamoDB.Query<ItemType>(keyConditionExpression, expressionAttributeValues, filterExpression);

Deleting data from DynamoDB

To delete data from DynamoDB use the method Delete with suitable parameters. For more information about the parameters, see the AWS documentation.

const keys = [
    {
        pk: 1
    },
    {
        pk: 2
    }
];

dynamoDB.Delete(keys);

Who do I talk to?

  • Team Spartans on slack channel

Release notes

v1.1.5 - 2022-08-22 - Extending base validations for address model- city, country, address line1.

v2.0.4 - 2023-03-12 - Change error message handling for third party communicator and authorization process.

v2.1.0 - 2023-04-21 - Added new base validation's rules and new text and custom validation helpers. Additional code improvements.

v4.0.2 - 2023-08-17 - Add json serialization for handling carrier response during error in third party connector.

v4.0.3 - 2023-08-24 - Changed error message format so that it won't be badly formatted on SE side.

v4.0.4 - 2023-08-25 - Removed new line characters from message so that it won't be badly formatted on SE side.

v4.1.0 - 2023-08-29 - POC; support for launch darkly flags added.

v4.1.1 - 2023-08-29 - fix to provide launch darkly key.

v4.1.2 - 2023-09-01 - fix to features for boolean values and additional logging added.

v.4.1.3 - 2023-09-08 - fix features flags to work asynchronically - Bartosz Musielak

v4.1.4 - 2023-10-09 - Added base methods for splitting ZPL and PDF labels, changed Queries method to not add '?' after each parameter

v4.2.0 - 2023-10-23 - Move @shipengine/connect-carrier-api, @shipengine/connect-runtime to peer dependencies

v4.2.1 - 2023-10-24 - Corrected workflow file for label splitting methods

v4.3.0 - 2023-10-25 - Add generic SOAP client - Marcin Karwat

v4.3.1 - 2023-10-25 - Brought back label splitting tests - Magdalena Niwczyk

v4.3.2 - 2023-10-30 - GOLD-2731 - Improve returned datetime for event_datetime parameter for track method.

v4.4.0 - 2023-11-14 - GOLD-2184 - Add generic DynamoDB client

v4.4.1 - 2024-03-22 - GOLD-4340 - Remove unnecessary request field in DefaultVoidImplementation response

v4.4.2 - 2024-04-24 - GOLD-5152 - Add a method to check if the country is from European Union - Bartosz Zurawski

4.4.2

15 days ago

4.4.1

2 months ago

4.3.2

6 months ago

4.4.0

6 months ago

4.3.1

7 months ago

4.1.3

8 months ago

4.0.4

9 months ago

4.1.0

8 months ago

4.3.0

7 months ago

4.2.1

7 months ago

4.1.2

8 months ago

4.0.3

9 months ago

4.2.0

7 months ago

4.1.1

8 months ago

4.0.2

9 months ago

4.0.1

10 months ago

2.0.9

1 year ago

2.1.0

1 year ago

2.0.8

1 year ago