Skip to content

Commit 624368a

Browse files
hive-botclaude
andcommitted
fix: read missing usage product fields as undefined
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 1ef3d6f commit 624368a

5 files changed

Lines changed: 41 additions & 16 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8686
- Added the API Pro fields of `GET /v2/usage` to `Usage`: `apiKeyCharacter`,
8787
`speechToTextMinutes` and `speechToSpeechMinutes` (`UsageDetail`), `products`
8888
(array of the new `UsageProduct` type), and `startTime` and `endTime`
89-
(`Date`). They are `undefined` when the response does not contain them.
89+
(`Date`). They are `undefined` when the response does not contain them, as
90+
are the `UsageProduct` fields missing from a `products` entry.
9091
`anyLimitReached()` still checks only the character, document and team
9192
document usage.
9293
- Added the `DeepLAdminClient` class for the Admin API. It must be created with

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1091,7 +1091,8 @@ For API Pro accounts, the `Usage` object also contains:
10911091
- `products`: an array of per-product usage, each with `productType` (for
10921092
example `'translate'`, `'write'`, `'speechToText'`), `billingUnit` (for
10931093
example `'characters'`, `'minutes'`), `apiKeyUnitCount` (this API key) and
1094-
`accountUnitCount` (the whole account).
1094+
`accountUnitCount` (the whole account). Each of these four fields is
1095+
`undefined` when the API omits it.
10951096
10961097
These fields are `undefined` when the response does not contain them, for
10971098
example for API Free accounts. `anyLimitReached()` does not check

‎src/parsing.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -584,15 +584,18 @@ function parseOptionalDate(value: unknown): Date | undefined {
584584
}
585585

586586
/**
587-
* Parses the products array of the usage API response; missing or malformed values get defaults.
587+
* Parses the products array of the usage API response; missing or malformed values are undefined.
588588
* @private
589589
*/
590590
function parseUsageProducts(products: unknown): UsageProduct[] | undefined {
591591
if (!Array.isArray(products)) return undefined;
592-
const str = (value: unknown) => (typeof value === 'string' ? value : '');
593-
const num = (value: unknown) => (typeof value === 'number' ? value : 0);
592+
const str = (value: unknown) => (typeof value === 'string' ? value : undefined);
593+
const num = (value: unknown) => (typeof value === 'number' ? value : undefined);
594594
return products.map((product) => {
595-
const p = (product ?? {}) as Record<string, unknown>;
595+
const p = (typeof product === 'object' && product !== null ? product : {}) as Record<
596+
string,
597+
unknown
598+
>;
596599
return {
597600
productType: str(p.product_type),
598601
billingUnit: str(p.billing_unit),

‎src/types.ts‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -645,14 +645,23 @@ export interface UsageDetail {
645645
* Usage of one product in the current billing period, part of the Usage object.
646646
*/
647647
export interface UsageProduct {
648-
/** The product, for example 'translate', 'write', 'speechToText' or 'speechToSpeech'. */
649-
readonly productType: string;
650-
/** The unit the product is billed in, for example 'characters' or 'minutes'. */
651-
readonly billingUnit: string;
652-
/** Units used for this product by this API key. */
653-
readonly apiKeyUnitCount: number;
654-
/** Units used for this product by the whole account. */
655-
readonly accountUnitCount: number;
648+
/**
649+
* The product, for example 'translate', 'write', 'speechToText' or 'speechToSpeech';
650+
* undefined if the response does not contain it.
651+
*/
652+
readonly productType?: string;
653+
/**
654+
* The unit the product is billed in, for example 'characters' or 'minutes'; undefined if the
655+
* response does not contain it.
656+
*/
657+
readonly billingUnit?: string;
658+
/** Units used for this product by this API key; undefined if the response does not contain it. */
659+
readonly apiKeyUnitCount?: number;
660+
/**
661+
* Units used for this product by the whole account; undefined if the response does not
662+
* contain it.
663+
*/
664+
readonly accountUnitCount?: number;
656665
}
657666

658667
/**

‎tests/offline/usage.test.ts‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,26 @@ describe('Translator.getUsage', () => {
143143
character_limit: 100,
144144
start_time: 'not a timestamp',
145145
end_time: 42,
146-
products: [{ product_type: 'translate' }],
146+
products: [{ product_type: 'translate', api_key_unit_count: '5' }, {}],
147147
});
148148

149149
const usage = await makeTranslator().getUsage();
150150

151151
expect(usage.character).toMatchObject({ count: 10, limit: 100 });
152152
expect([usage.startTime, usage.endTime]).toEqual([undefined, undefined]);
153153
expect(usage.products).toEqual([
154-
{ productType: 'translate', billingUnit: '', apiKeyUnitCount: 0, accountUnitCount: 0 },
154+
{
155+
productType: 'translate',
156+
billingUnit: undefined,
157+
apiKeyUnitCount: undefined,
158+
accountUnitCount: undefined,
159+
},
160+
{
161+
productType: undefined,
162+
billingUnit: undefined,
163+
apiKeyUnitCount: undefined,
164+
accountUnitCount: undefined,
165+
},
155166
]);
156167
});
157168
});

0 commit comments

Comments
 (0)