Skip to content

Conversation

@NabDevs
Copy link
Contributor

@NabDevs NabDevs commented Oct 6, 2025

Implements a Capabilities API client to enable dynamic discovery of carrier-specific functionality, preparing the SDK architecture for future configuration refactoring.

Implementation

  • Generated Core API client from the OpenAPI spec
    Output in src/Services/CoreApi/Generated/Shipments
    Generated via Docker using OpenAPI Generator
    Regeneration via composer generate:api (generated code committed)

  • SDK integration
    HttpCapabilitiesClient, CapabilitiesMapper, CapabilitiesClientFactory
    CapabilitiesService and CapabilitiesServiceInterface

  • Models
    CapabilitiesRequest (immutable request builder)
    CapabilitiesResponse (read-only access)

  • Coverage
    Full request support for Core API v2 fields (sender, options, physicalProperties)
    Response mapping for package types, delivery types, and shipment option keys

  • Enums
    Removed manual SDK enums
    Uses generated Core API enums (RefTypesCarrierV2, RefTypesDeliveryTypeV2, etc.) for validation and normalization

  • Tooling and CI
    Docker-based OpenAPI generation via existing composer script
    Added a GitHub Actions workflow that periodically regenerates the client and opens a PR when changes are detected

  • Tests
    Unit tests
    Live smoke tests (skipped without API keys)

Usage Examples:

Recommended: Via Service

use MyParcelNL\Sdk\Services\Capabilities\CapabilitiesService;
use MyParcelNL\Sdk\Model\Capabilities\CapabilitiesRequest;
use MyParcel\CoreApi\Generated\Capabilities\Model\RefTypesCarrierV2;

$service = new CapabilitiesService();
$capabilities = $service->get(
    CapabilitiesRequest::forCountry('NL')
        ->withShopId(18)
        ->withCarrier(RefTypesCarrierV2::POSTNL)
        ->withSender(['country_code' => 'NL', 'is_business' => true])
        ->withOptions(['requires_signature' => new \stdClass()])
        ->withPhysicalProperties(['weight' => ['value' => 500.0, 'unit' => 'g']])
);

// Access results
$packageTypes = $capabilities->getPackageTypes();     // ['PACKAGE', 'MAILBOX', ...]
$deliveryTypes = $capabilities->getDeliveryTypes();   // ['STANDARD_DELIVERY', 'EVENING_DELIVERY', ...]
$options = $capabilities->getShipmentOptions();       // ['signature', 'only_recipient', ...]

Direct Client Access

use MyParcelNL\Sdk\Services\CoreApi\HttpCapabilitiesClient;
use MyParcelNL\Sdk\Model\Capabilities\CapabilitiesMapper;

$client = new HttpCapabilitiesClient(new CapabilitiesMapper());
$capabilities = $client->getCapabilities($request);

INT-1054

@codecov
Copy link

codecov bot commented Oct 6, 2025

Codecov Report

❌ Patch coverage is 62.42038% with 118 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.54%. Comparing base (5f961a4) to head (47b5543).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/Model/Shipment/Shipment.php 0.00% 54 Missing ⚠️
src/Services/CoreApi/CapabilitiesClientFactory.php 0.00% 35 Missing ⚠️
src/Model/Capabilities/CapabilitiesMapper.php 80.40% 29 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #551      +/-   ##
============================================
+ Coverage     56.29%   56.54%   +0.24%     
- Complexity     1863     1988     +125     
============================================
  Files           118      125       +7     
  Lines          5437     5764     +327     
============================================
+ Hits           3061     3259     +198     
- Misses         2376     2505     +129     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@NabDevs NabDevs marked this pull request as ready for review October 7, 2025 06:27
@NabDevs NabDevs requested a review from a team as a code owner October 7, 2025 06:27
@FreekVR
Copy link
Contributor

FreekVR commented Oct 7, 2025

Please also ensure the commit message AND PR title conform to a conventional commit format and are suitable for inclusion into changelogs

@NabDevs NabDevs changed the title Capabilities implementation v1 feat(capabilities): add capabilities functionality to the SDK Oct 10, 2025
@NabDevs NabDevs requested a review from FreekVR October 10, 2025 09:20
@NabDevs NabDevs force-pushed the feat/capabilities-client branch 2 times, most recently from 0c86d99 to d29eda7 Compare October 13, 2025 09:48
Copy link
Contributor

@joerivanveen joerivanveen left a comment

Choose a reason for hiding this comment

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

As discussed would be nice to exclude the generated directory from coverage reports, to keep the data understandable.

@NabDevs NabDevs requested a review from joerivanveen October 13, 2025 17:36
CONTRIBUTING.md Outdated
This command runs `swaggerapi/swagger-codegen-cli-v3` inside a Docker container, using the specification at:

```
https://api.myparcel.nl/openapi.yaml
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't have to be in this PR right now, but we should have a ticket for and a means to generate a client for the acceptance API and possibly testing as well. Ideally in a way that does not require a completely new generation of a new client (e.g. being able to switch at runtime to a pregenerated acceptance client?)

Does the swagger codegen offer any solutions 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.

@FreekVR If the acceptance spec would be the same as prod. we'll keep one generated client and switch runtime via an env flag in the factory (using Configuration::setHost()). Swagge also supports server switching when multiple servers are defined in the spec, but our Core API only defines one. If the specs differ, we can generate a second client under a different namespace and let the factory pick the correct one based on the environment flag.

I'll write a ticket if you want me to.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would be good to add a ticket -- any new or changed fields would yield a new schema, and the only way to prepare our client code would be to generate a new api client as well. Perhaps we could simply solve this with a branching strategy, but would be good to create ticket to think about this and at the very least document that approach there.

@myparcel-bot myparcel-bot bot force-pushed the feat/capabilities-client branch from 9c87a6d to b8ac8b6 Compare October 22, 2025 08:22
@NabDevs NabDevs enabled auto-merge October 24, 2025 11:39
@myparcel-bot myparcel-bot bot force-pushed the feat/capabilities-client branch 2 times, most recently from e65e24d to 807bf50 Compare November 11, 2025 16:34
@myparcel-bot myparcel-bot bot force-pushed the feat/capabilities-client branch 5 times, most recently from ad5ed2a to 56bedfe Compare November 24, 2025 14:55
@myparcel-bot myparcel-bot bot force-pushed the feat/capabilities-client branch from 56bedfe to 41bffa7 Compare November 28, 2025 11:11
@myparcel-bot myparcel-bot bot force-pushed the feat/capabilities-client branch 3 times, most recently from 67c9fde to cb43c00 Compare December 17, 2025 10:15
@myparcel-bot myparcel-bot bot force-pushed the feat/capabilities-client branch from cb43c00 to 4deb8d4 Compare January 5, 2026 08:51
@NabDevs NabDevs force-pushed the feat/capabilities-client branch 3 times, most recently from 2d3eded to 4dd5073 Compare January 14, 2026 16:32
@myparcel-bot myparcel-bot bot force-pushed the feat/capabilities-client branch 2 times, most recently from 37fe840 to 2eb3d36 Compare January 19, 2026 15:00
Implements a Capabilities API client to enable dynamic discovery of carrier-specific functionality, preparing the SDK architecture for future configuration refactoring.

Phase 1:

- Generated client from Core API spec (Located in src/Services/CoreApi/Generated/Capabilities/).
- Integration layer: HttpCapabilitiesClient, CapabilitiesMapper (SDK <-> Core API transformation), CapabilitiesClientFactory.
- Service Facade: CapabilitiesService & CapabilitiesServiceInterface
- Request/Response models: CapabilitiesRequest (Immutable request builder) & CapabilitiesResponse with Built-in input normalization for flexibility.
- Type safety 'Enums': Carrier, DeliveryType, Direction, PackageType, TransactionType. All enums include normalize() methods for flexible input handling.
- Test coverage: Unit tests: Service pass-through behaviour, Integration tests: Request/response mapping logic and Live Smoke Tests: Real API validation (auto skipped without API keys).

Follow-up work for Phase 2:

- Add missing Core API fields:
The following fields are part of the Core Capabilities v2 spec but are intentionally postponed for clarity and maintainability.
They introduce structural complexity that will be handled in the next iteration.
  • sender (simple object mapping)
  • physicalProperties (requires value/unit data classes)
  • options (dynamic shipment options map)
- Refactor static SDK configuration to fetch capabilities dynamically
- Add caching, validation, and developer documentation

INT-1054
…with generated enums and all API fields

- Replaced Model/Capabilities/Enum/* with generated RefTypes* models
- Added sender, options, physicalProperties to CapabilitiesRequest and CapabilitiesMapper
- Added tests for new functionality
- Removed TODO markers - implementation now matches Core API v2 spec completely
- Added OpenAPI generator dev dependency and composer script for API updates

INT-1054
- Switch from Swagger Codegen to OpenAPI Generator (supports OpenAPI 3.1)
- Rename namespace from Capabilities to Shipments
- Create scripts/fix-generated-code.sh for PHP 7.4 ArrayAccess compatibility
- Integrate post-processing into composer generate:api command
- Update CONTRIBUTING.md documentation
- Remove phpLegacySupport option (not valid)
- Add fix-generated-php74.sh script to remove PHP 8.0+ type hints
- Fix all generated models to be PHP 7.4 compatible
@myparcel-bot myparcel-bot bot force-pushed the feat/capabilities-client branch from 2eb3d36 to ae3575c Compare January 19, 2026 15:01
- Add openapi-generator-mapper.yml with mappings for Capabilities V2 models
- Update CapabilitiesMapper to use shorter class names
- Maps verbose names like CapabilitiesPostCapabilitiesRequestV2PhysicalPropertiesHeight to PhysicalPropertiesHeight
- Generated models now use shorter, cleaner names
- CapabilitiesRecipient, CapabilitiesSender, etc. instead of verbose V2 names
- PhysicalPropertiesHeight/Length/Width/Weight instead of full nested names
- Remove mixed type hints not supported in PHP 7.4
- Applied via scripts/fix-generated-php74.sh
- Add fix-generated-php74 step to composer generate:api
- Ensures generated code is always PHP 7.4 compatible
- Remove docker fix from composer script (script not in container)
- Keep fix step in workflow where it runs directly on runner
- Mappings still apply via docker-compose config
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think its intentional to remove all the .iea files. Please restore these as long as we haven't replaced them with alternative solutions.

with:
fetch-depth: 0

- name: Set up PHP + Composer
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest using myparcelnl/php-xd:7.4 which is our own image with the correct PHP version we want to support and comes with composer

- name: Generate API client
run: composer generate:api

- name: Fix generated code for PHP 7.4 compatibility
Copy link
Contributor

Choose a reason for hiding this comment

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

If we use a PHP 7.4 image above, it should result in a version of the generator being installed which outputs compatible code - and that would make this step redundant.

*
* @see \MyParcelNL\Sdk\Mapper\CapabilitiesMapper
*/
class CapabilitiesRequest
Copy link
Contributor

Choose a reason for hiding this comment

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

What exactly is the advantage of using this over ex. CapabilitiesPostCapabilitiesRequestV2 directly in code consuming our SDK?

$options = new CoreOptionsV2();

// Map all available options - empty values mean "enabled"
foreach ($optionsData as $key => $value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This mapping would need updating for each new or changed option. Also not a fan of the keys being typed as just strings here.

$stack->push(Middleware::mapRequest(function (\Psr\Http\Message\RequestInterface $request) {
$path = (string) $request->getUri()->getPath();
if (false !== strpos($path, self::CAPABILITIES_PATH)) {
return $request->withHeader('Accept', self::ACCEPT_V2);
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally, we'd let external SDK consumers decide which version to use - and we'd decide to only use v2 ourselves. Unless that would cause a lot of extra manual mappings on the SDK end now.

"MyParcelNL\\Sdk\\": "src/"
}
"MyParcelNL\\Sdk\\": "src/",
"MyParcel\\CoreApi\\Generated\\Shipments\\": "src/Services/CoreApi/Generated/Shipments/lib"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest the prefix here should still be MyParcelNL\Sdk(\CoreApi\Generated\Shipments)?

"MyParcelNL\\Sdk\\": "src/",
"MyParcel\\CoreApi\\Generated\\Shipments\\": "src/Services/CoreApi/Generated/Shipments/lib"
},
"exclude-from-classmap": [
Copy link
Contributor

Choose a reason for hiding this comment

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

double check from my end: this doesn't hinder external use of the generated code?

command: [ 'composer', 'test:coverage' ]

generate-api:
image: openapitools/openapi-generator-cli:latest
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest switching to an older version for PHP 7.4 compatibility

See https://github.com/OpenAPITools/openapi-generator/pull/17826/changes

Version 7.12.0 should work (instead of latest)

# Maps inline schema names to shorter, cleaner class names

inlineSchemaNameMappings:
# Capabilities V2 - used in CapabilitiesMapper
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest adding the v2 somewhere in the class name to prevent a possible collision with v1 or potential future v3 names.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants