connect-carrier-api-utils
v4.5.13
Published
Connect carrier API utils package
Downloads
1,412
Readme
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.
- Make a reference to services definition data. Define when the service is selected if there should be custom logic behind that.
- Connect services with validations. Determinate validations for each service.
- 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 method | Arguments | Description
|-----------------------------------------------|-----------------------------------------------------------------------------------|--------------------------------------------------------------------------|
| CustomsItemsRequiredIfOneSelectedValidator
| None
| If any customs value is selected ensure all of them should be provided
| LabelSizeAndFormatValidator
| supportedLayouts: LabelLayouts[], supportedDocuments: DocumentFormat[]
| If layout or documents is not supported the error will be returned.
|
| MandatoryAddressParametersValidator
| addressType: AddressType, fieldsToValidate: AddressFields
| Chose which address should be validate, and which parameter should be provided.
| NumberOfPackageValidator
| max: number, min: number = 0
| The number of the package should be in range provided in arguments list
| ParcelWeightValidator
| minWeight: number, maxWeight: number
| Weight of the parcels should be in range provided in arguments list
| CheckAllowedAddressCountry
| allowedCountries: string[], addressType: AddressType = AddressType.Receiver
| Check country code for provided address type is allowed for provided values list
| ValidateParametersBySchema
| validateSchema: object
| Validate any object by provided valid JSON schema
| CheckPackagesMaxDimensions
| maxDimensions: 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:
- Inheritance
ThirdPartyConnector
to make sure logs will contains correct module oriented prefix and refresh token logic will be implemented. - 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:
| Method | Description
|------- | -----------
| Method
| Set method of the request
| Url
| Set url of the request
| Body
| Set body of the request
| Headers
| Set headers of the request
| Query
| Set 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.
| Method | Description
| --- | ---
| 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
| Method | Description
|-------------------------|-------------
| GetFirstNotEmpty
| Pass the number of strings and it will return the first not empty string.
| CheckAndSubstringText
| Check if the text is longer than the max length and if so, it will be truncated.
| FormatUrl
| Format url with query parameters.
| GetSafeString
| Get safe string from the string.
| HandleError
| Handle error in preferred by SE Connect way.
| LogInfo
| Log info in preferred by SE Connect way.
| LogError
| Log error in preferred by SE Connect way.
| GetGUID
| Get GUID.
| GetSafeValueFromMap
| Get safe value from map.
| GetTrackingNumber
| Get tracking number.
| GetValueFromMetadata
| Get value from metadata in a safe way.
| CheckIfMandatoryParametersAreProvided
| Check list of the parameters if are provided
| FormatUtcDate
| Formats the given date to the given format converted to UTC time zone
| StringToBase64
| Encode string to base64.
| ArrayBufferToBase64
| Encode provided ArrayBuffer to base64 string
| GetSortedPackageDimensions
| Returns sorted package dimensions in ascending or descending order
| IsValueAllowed
| Check 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();
| Class | Description
|---------------------------------|------------
| FakeAddress
| Fake object for address.
| FakePackage
| Fake object for package.
| FakeCreateLabelRequestBuilder
| Fake object for create label.
| FakeCreateManifestRequestBuilder
| Fake object for create manifest.
| FakeCustomItem
| Fake object for custom item.
| FakeGetTrackRequest
| Fake object for get track.
| FakeRegisterRequestBuilder
| Fake object for register.
| FakeShipFrom
| Fake object for ship from.
| FakeShipTo
| Fake object for ship to.
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
v4.5.0 - 2024-06-14 - GOLD-6337 - Add AlternativeIdentifier - Kajetan Starobrzanski
v4.5.7 - 2024-07-26 - GOLD-7453 - Fix response logging and error handling in base ThirdPartyConnector implementation - Jakub Hypki
v4.5.8 - 2024-08-02 - GOLD-7453 - Add method to handle response stringifying and improve error handling in ThirdPartyConenctor - Jakub Hypki
v4.5.9 - 2024-09-02 - GOLD-8215 - Remove FF (FeatureFlag) support - Karolina Kleciak