@scaleway/sdk-client 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +2 -0
  2. package/dist/bridge.js +8 -0
  3. package/dist/helpers/__tests__/is-browser.browser.test.js +10 -0
  4. package/dist/helpers/__tests__/is-browser.node.test.js +10 -0
  5. package/dist/helpers/__tests__/json.test.js +48 -0
  6. package/dist/helpers/__tests__/marshalling.test.js +177 -0
  7. package/dist/helpers/is-browser.js +3 -1
  8. package/dist/helpers/is-response.js +3 -1
  9. package/dist/helpers/json.js +10 -4
  10. package/dist/helpers/marshalling.js +7 -5
  11. package/dist/index.js +4 -4
  12. package/dist/internal/async/__tests__/interval-retrier.test.js +121 -0
  13. package/dist/internal/async/__tests__/sleep.test.js +19 -0
  14. package/dist/internal/async/interval-retrier.d.ts +1 -1
  15. package/dist/internal/async/interval-retrier.js +33 -3
  16. package/dist/internal/async/sleep.js +3 -1
  17. package/dist/internal/interceptors/__tests__/composer.test.js +48 -0
  18. package/dist/internal/interceptors/__tests__/helpers.test.js +27 -0
  19. package/dist/internal/interceptors/composer.js +5 -3
  20. package/dist/internal/interceptors/helpers.js +4 -2
  21. package/dist/internal/interceptors/types.js +0 -0
  22. package/dist/internal/logger/__tests__/index.test.js +209 -0
  23. package/dist/internal/logger/console-logger.js +2 -0
  24. package/dist/internal/logger/index.js +5 -3
  25. package/dist/internal/logger/level-resolver.js +4 -2
  26. package/dist/internal/logger/logger.js +0 -0
  27. package/dist/internal/validations/__tests__/string-validation.test.js +98 -0
  28. package/dist/internal/validations/string-validation.js +14 -9
  29. package/dist/internals.js +9 -8
  30. package/dist/package.js +2 -1
  31. package/dist/scw/__tests__/api.test.js +19 -0
  32. package/dist/scw/__tests__/auth.test.js +57 -0
  33. package/dist/scw/__tests__/client-ini-factory.test.js +220 -0
  34. package/dist/scw/__tests__/client-ini-profile.test.js +70 -0
  35. package/dist/scw/__tests__/client-settings.test.js +51 -0
  36. package/dist/scw/__tests__/client.test.js +59 -0
  37. package/dist/scw/__tests__/custom-marshalling.test.js +168 -0
  38. package/dist/scw/api.js +2 -0
  39. package/dist/scw/auth.js +17 -6
  40. package/dist/scw/client-ini-factory.js +9 -7
  41. package/dist/scw/client-ini-profile.js +3 -1
  42. package/dist/scw/client-settings.js +3 -1
  43. package/dist/scw/client.js +4 -2
  44. package/dist/scw/constants.js +6 -4
  45. package/dist/scw/custom-marshalling.js +17 -15
  46. package/dist/scw/custom-types.js +2 -0
  47. package/dist/scw/errors/__tests__/scw-error.test.js +41 -0
  48. package/dist/scw/errors/__tests__/types.test.js +16 -0
  49. package/dist/scw/errors/error-parser.js +3 -1
  50. package/dist/scw/errors/non-standard/__tests__/index.test.js +123 -0
  51. package/dist/scw/errors/non-standard/invalid-request-mapper.js +3 -1
  52. package/dist/scw/errors/non-standard/unknown-resource-mapper.js +3 -1
  53. package/dist/scw/errors/scw-error-from-json.js +0 -0
  54. package/dist/scw/errors/scw-error.js +2 -0
  55. package/dist/scw/errors/standard/__tests__/index.test.js +329 -0
  56. package/dist/scw/errors/standard/already-exists-error.js +2 -0
  57. package/dist/scw/errors/standard/denied-authentication-error.js +2 -0
  58. package/dist/scw/errors/standard/index.js +3 -1
  59. package/dist/scw/errors/standard/invalid-arguments-error.js +2 -0
  60. package/dist/scw/errors/standard/out-of-stock-error.js +2 -0
  61. package/dist/scw/errors/standard/permissions-denied-error.js +2 -0
  62. package/dist/scw/errors/standard/precondition-failed-error.js +2 -0
  63. package/dist/scw/errors/standard/quotas-exceeded-error.js +2 -0
  64. package/dist/scw/errors/standard/resource-expired-error.js +2 -0
  65. package/dist/scw/errors/standard/resource-locked-error.js +2 -0
  66. package/dist/scw/errors/standard/resource-not-found-error.js +2 -0
  67. package/dist/scw/errors/standard/too-many-requests-error.js +2 -0
  68. package/dist/scw/errors/standard/transient-state-error.js +2 -0
  69. package/dist/scw/errors/types.js +3 -1
  70. package/dist/scw/fetch/__tests__/build-fetcher.test.js +114 -0
  71. package/dist/scw/fetch/__tests__/http-dumper.test.js +45 -0
  72. package/dist/scw/fetch/__tests__/http-interceptors.test.js +60 -0
  73. package/dist/scw/fetch/__tests__/resource-paginator.test.js +110 -0
  74. package/dist/scw/fetch/__tests__/response-parser.test.js +94 -0
  75. package/dist/scw/fetch/build-fetcher.js +6 -4
  76. package/dist/scw/fetch/http-dumper.js +4 -2
  77. package/dist/scw/fetch/http-interceptors.js +5 -3
  78. package/dist/scw/fetch/resource-paginator.js +6 -4
  79. package/dist/scw/fetch/response-parser.js +5 -3
  80. package/dist/scw/fetch/types.js +0 -0
  81. package/dist/scw/locality.js +2 -0
  82. package/dist/vendor/base64/index.d.js +0 -0
  83. package/dist/vendor/base64/index.js +2 -0
  84. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { hasAuthenticationSecrets } from "./client-ini-profile.js";
2
2
  import { authenticateWithSecrets } from "./auth.js";
3
+ //#region src/scw/client-ini-factory.ts
3
4
  /**
4
5
  * Instantiates the SDK from a configuration {@link Profile}.
5
6
  *
@@ -10,7 +11,7 @@ import { authenticateWithSecrets } from "./auth.js";
10
11
  *
11
12
  * @public
12
13
  */
13
- const withProfile = (profile) => (settings) => {
14
+ var withProfile = (profile) => (settings) => {
14
15
  const newSettings = { ...settings };
15
16
  if (profile.apiURL) newSettings.apiURL = profile.apiURL;
16
17
  if (profile.defaultOrganizationId) newSettings.defaultOrganizationId = profile.defaultOrganizationId;
@@ -30,7 +31,7 @@ const withProfile = (profile) => (settings) => {
30
31
  *
31
32
  * @public
32
33
  */
33
- const withHTTPClient = (httpClient) => (settings) => ({
34
+ var withHTTPClient = (httpClient) => (settings) => ({
34
35
  ...settings,
35
36
  httpClient
36
37
  });
@@ -44,7 +45,7 @@ const withHTTPClient = (httpClient) => (settings) => ({
44
45
  *
45
46
  * @public
46
47
  */
47
- const withDefaultPageSize = (defaultPageSize) => (settings) => ({
48
+ var withDefaultPageSize = (defaultPageSize) => (settings) => ({
48
49
  ...settings,
49
50
  defaultPageSize
50
51
  });
@@ -58,7 +59,7 @@ const withDefaultPageSize = (defaultPageSize) => (settings) => ({
58
59
  *
59
60
  * @public
60
61
  */
61
- const withUserAgent = (userAgent) => (settings) => ({
62
+ var withUserAgent = (userAgent) => (settings) => ({
62
63
  ...settings,
63
64
  userAgent
64
65
  });
@@ -72,7 +73,7 @@ const withUserAgent = (userAgent) => (settings) => ({
72
73
  *
73
74
  * @public
74
75
  */
75
- const withUserAgentSuffix = (userAgent) => (settings) => ({
76
+ var withUserAgentSuffix = (userAgent) => (settings) => ({
76
77
  ...settings,
77
78
  userAgent: settings.userAgent ? `${settings.userAgent} ${userAgent}` : userAgent
78
79
  });
@@ -118,14 +119,14 @@ const withUserAgentSuffix = (userAgent) => (settings) => ({
118
119
  *
119
120
  * @public
120
121
  */
121
- const withAdditionalInterceptors = (interceptors) => (settings) => ({
122
+ var withAdditionalInterceptors = (interceptors) => (settings) => ({
122
123
  ...settings,
123
124
  interceptors: settings.interceptors.concat(interceptors)
124
125
  });
125
126
  /**
126
127
  * Instantiates the SDK with legacy interceptors.
127
128
  */
128
- const withLegacyInterceptors = () => (settings) => {
129
+ var withLegacyInterceptors = () => (settings) => {
129
130
  if (!settings.requestInterceptors && !settings.responseInterceptors) return settings;
130
131
  const allInterceptors = settings.interceptors.concat((settings.requestInterceptors ?? []).map((obj) => ({ request: obj })), (settings.responseInterceptors ?? []).map((obj) => ({ response: obj })));
131
132
  return {
@@ -133,4 +134,5 @@ const withLegacyInterceptors = () => (settings) => {
133
134
  interceptors: allInterceptors
134
135
  };
135
136
  };
137
+ //#endregion
136
138
  export { withAdditionalInterceptors, withDefaultPageSize, withHTTPClient, withLegacyInterceptors, withProfile, withUserAgent, withUserAgentSuffix };
@@ -1,4 +1,5 @@
1
1
  import { isAccessKey, isSecretKey } from "../internal/validations/string-validation.js";
2
+ //#region src/scw/client-ini-profile.ts
2
3
  /**
3
4
  * Verifies that the payload contains both the accessKey and the secretKey.
4
5
  *
@@ -7,7 +8,7 @@ import { isAccessKey, isSecretKey } from "../internal/validations/string-validat
7
8
  *
8
9
  * @internal
9
10
  */
10
- const hasAuthenticationSecrets = (obj) => typeof obj.accessKey === "string" && obj.accessKey !== "" && typeof obj.secretKey === "string" && obj.secretKey !== "";
11
+ var hasAuthenticationSecrets = (obj) => typeof obj.accessKey === "string" && obj.accessKey !== "" && typeof obj.secretKey === "string" && obj.secretKey !== "";
11
12
  /**
12
13
  * Asserts the format of secrets.
13
14
  *
@@ -24,4 +25,5 @@ function assertValidAuthenticationSecrets(obj) {
24
25
  if (!isAccessKey(obj.accessKey)) throw new Error(`Invalid access key format '${obj.accessKey}', expected SCWXXXXXXXXXXXXXXXXX format. See https://www.scaleway.com/en/docs/identity-and-access-management/iam/how-to/create-api-keys/`);
25
26
  if (!isSecretKey(obj.secretKey)) throw new Error(`Invalid secret key format '${obj.secretKey}', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. See https://www.scaleway.com/en/docs/identity-and-access-management/iam/how-to/create-api-keys/`);
26
27
  }
28
+ //#endregion
27
29
  export { assertValidAuthenticationSecrets, hasAuthenticationSecrets };
@@ -1,4 +1,5 @@
1
1
  import { isOrganizationId, isProjectId, isRegion, isURL, isZone } from "../internal/validations/string-validation.js";
2
+ //#region src/scw/client-settings.ts
2
3
  /**
3
4
  * Validates the content of a {@link Settings} object.
4
5
  *
@@ -7,7 +8,7 @@ import { isOrganizationId, isProjectId, isRegion, isURL, isZone } from "../inter
7
8
  *
8
9
  * @internal
9
10
  */
10
- const assertValidSettings = (obj) => {
11
+ var assertValidSettings = (obj) => {
11
12
  if (obj.defaultOrganizationId !== void 0) {
12
13
  if (typeof obj.defaultOrganizationId !== "string" || obj.defaultOrganizationId.length === 0) throw new Error("Default organization ID cannot be empty");
13
14
  if (!isOrganizationId(obj.defaultOrganizationId)) throw new Error(`Invalid organization ID format '${obj.defaultOrganizationId}', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`);
@@ -24,4 +25,5 @@ const assertValidSettings = (obj) => {
24
25
  if (obj.defaultPageSize !== void 0 && (typeof obj.defaultPageSize !== "number" || Number.isNaN(obj.defaultPageSize) || obj.defaultPageSize <= 0)) throw new Error(`Invalid defaultPageSize ${obj.defaultPageSize}: it should be a number above 0`);
25
26
  if (typeof obj.userAgent !== "string") throw new Error(`Invalid User-Agent`);
26
27
  };
28
+ //#endregion
27
29
  export { assertValidSettings };
@@ -3,6 +3,7 @@ import { userAgent, version } from "./constants.js";
3
3
  import { withAdditionalInterceptors, withLegacyInterceptors, withProfile } from "./client-ini-factory.js";
4
4
  import { assertValidSettings } from "./client-settings.js";
5
5
  import { buildFetcher } from "./fetch/build-fetcher.js";
6
+ //#region src/scw/client.ts
6
7
  /** Default {@link Settings} values. */
7
8
  var DEFAULT_SETTINGS = {
8
9
  apiURL: "https://api.scaleway.com",
@@ -34,7 +35,7 @@ var DEFAULT_SETTINGS = {
34
35
  *
35
36
  * @public
36
37
  */
37
- const createAdvancedClient = (...configs) => {
38
+ var createAdvancedClient = (...configs) => {
38
39
  const settings = configs.concat([withLegacyInterceptors()]).reduce((currentSettings, config) => config(currentSettings), DEFAULT_SETTINGS);
39
40
  assertValidSettings(settings);
40
41
  getLogger().info(`init Scaleway SDK version ${version}`);
@@ -78,5 +79,6 @@ const createAdvancedClient = (...configs) => {
78
79
  *
79
80
  * @public
80
81
  */
81
- const createClient = (settings = {}) => createAdvancedClient(withProfile(settings), withAdditionalInterceptors(settings.interceptors ?? []));
82
+ var createClient = (settings = {}) => createAdvancedClient(withProfile(settings), withAdditionalInterceptors(settings.interceptors ?? []));
83
+ //#endregion
82
84
  export { createAdvancedClient, createClient };
@@ -1,6 +1,8 @@
1
1
  import package_default from "../package.js";
2
- const { version } = package_default;
3
- const userAgent = `scaleway-sdk-js/${version}`;
4
- const SESSION_HEADER_KEY = "x-session-token";
5
- const AUTH_HEADER_KEY = "x-auth-token";
2
+ //#region src/scw/constants.ts
3
+ var { version } = package_default;
4
+ var userAgent = `scaleway-sdk-js/${version}`;
5
+ var SESSION_HEADER_KEY = "x-session-token";
6
+ var AUTH_HEADER_KEY = "x-auth-token";
7
+ //#endregion
6
8
  export { AUTH_HEADER_KEY, SESSION_HEADER_KEY, userAgent, version };
@@ -2,12 +2,13 @@ import { camelizeKeys, isJSONObject } from "../helpers/json.js";
2
2
  import { unmarshalArrayOfObject, unmarshalDate } from "../helpers/marshalling.js";
3
3
  import { fromByteArray } from "../vendor/base64/index.js";
4
4
  import { Decimal } from "./custom-types.js";
5
+ //#region src/scw/custom-marshalling.ts
5
6
  /**
6
7
  * Unmarshals {@link Money}
7
8
  *
8
9
  * @internal
9
10
  */
10
- const unmarshalMoney = (data) => {
11
+ var unmarshalMoney = (data) => {
11
12
  if (!isJSONObject(data)) throw new TypeError(`Unmarshalling the type 'Money' failed as data isn't a dictionary.`);
12
13
  return {
13
14
  currencyCode: data.currency_code,
@@ -20,7 +21,7 @@ const unmarshalMoney = (data) => {
20
21
  *
21
22
  * @internal
22
23
  */
23
- const unmarshalServiceInfo = (data) => {
24
+ var unmarshalServiceInfo = (data) => {
24
25
  if (!isJSONObject(data)) throw new TypeError(`Unmarshalling the type 'ServiceInfo' failed as data isn't a dictionary.`);
25
26
  return {
26
27
  description: data.description,
@@ -34,7 +35,7 @@ const unmarshalServiceInfo = (data) => {
34
35
  *
35
36
  * @internal
36
37
  */
37
- const unmarshalScwFile = (data) => {
38
+ var unmarshalScwFile = (data) => {
38
39
  if (!isJSONObject(data)) throw new TypeError(`Unmarshalling the type 'ScwFile' failed as data isn't a dictionary.`);
39
40
  return {
40
41
  content: data.content,
@@ -51,7 +52,7 @@ const unmarshalScwFile = (data) => {
51
52
  *
52
53
  * @internal
53
54
  */
54
- const unmarshalTimeSeriesPoint = (data) => {
55
+ var unmarshalTimeSeriesPoint = (data) => {
55
56
  if (!Array.isArray(data)) throw new TypeError(`Unmarshalling the type 'TimeSeriesPoint' failed as data isn't an array.`);
56
57
  return {
57
58
  timestamp: unmarshalDate(data[0]),
@@ -63,7 +64,7 @@ const unmarshalTimeSeriesPoint = (data) => {
63
64
  *
64
65
  * @internal
65
66
  */
66
- const unmarshalTimeSeries = (data) => {
67
+ var unmarshalTimeSeries = (data) => {
67
68
  if (!isJSONObject(data)) throw new TypeError(`Unmarshalling the type 'TimeSeries' failed as data isn't a dictionary.`);
68
69
  return {
69
70
  metadata: data.metadata,
@@ -76,7 +77,7 @@ const unmarshalTimeSeries = (data) => {
76
77
  *
77
78
  * @internal
78
79
  */
79
- const unmarshalDecimal = (data) => {
80
+ var unmarshalDecimal = (data) => {
80
81
  if (!(typeof data === "object")) throw new TypeError(`Unmarshalling the type 'Decimal' failed as data isn't an object.`);
81
82
  if (data === null) return null;
82
83
  if (!("value" in data)) throw new TypeError(`Unmarshalling the type 'Decimal' failed as data object does not have a 'value' key.`);
@@ -88,7 +89,7 @@ const unmarshalDecimal = (data) => {
88
89
  *
89
90
  * @internal
90
91
  */
91
- const marshalScwFile = (obj) => ({
92
+ var marshalScwFile = (obj) => ({
92
93
  content: obj.content,
93
94
  content_type: obj.contentType,
94
95
  name: obj.name
@@ -98,7 +99,7 @@ const marshalScwFile = (obj) => ({
98
99
  *
99
100
  * @internal
100
101
  */
101
- const marshalBlobToScwFile = async (blob) => ({
102
+ var marshalBlobToScwFile = async (blob) => ({
102
103
  content: fromByteArray(new Uint8Array(await blob.arrayBuffer())),
103
104
  content_type: blob.type,
104
105
  name: "file"
@@ -108,7 +109,7 @@ const marshalBlobToScwFile = async (blob) => ({
108
109
  *
109
110
  * @internal
110
111
  */
111
- const marshalMoney = (obj) => ({
112
+ var marshalMoney = (obj) => ({
112
113
  currency_code: obj.currencyCode,
113
114
  nanos: obj.nanos,
114
115
  units: obj.units
@@ -118,7 +119,7 @@ const marshalMoney = (obj) => ({
118
119
  *
119
120
  * @internal
120
121
  */
121
- const marshalTimeSeriesPoint = (obj) => ({
122
+ var marshalTimeSeriesPoint = (obj) => ({
122
123
  timestamp: obj.timestamp?.toISOString(),
123
124
  value: obj.value
124
125
  });
@@ -127,7 +128,7 @@ const marshalTimeSeriesPoint = (obj) => ({
127
128
  *
128
129
  * @internal
129
130
  */
130
- const marshalTimeSeries = (obj) => ({
131
+ var marshalTimeSeries = (obj) => ({
131
132
  metadata: obj.metadata,
132
133
  name: obj.name,
133
134
  points: obj.points.map((elt) => marshalTimeSeriesPoint(elt))
@@ -137,7 +138,7 @@ const marshalTimeSeries = (obj) => ({
137
138
  *
138
139
  * @internal
139
140
  */
140
- const marshalDecimal = (obj) => ({ value: obj.toString() });
141
+ var marshalDecimal = (obj) => ({ value: obj.toString() });
141
142
  /**
142
143
  * Unmarshals record to convert iso dates from string to Dates.
143
144
  *
@@ -147,7 +148,7 @@ const marshalDecimal = (obj) => ({ value: obj.toString() });
147
148
  *
148
149
  * @internal
149
150
  */
150
- const unmarshalDates = (obj, keys) => {
151
+ var unmarshalDates = (obj, keys) => {
151
152
  if (Array.isArray(obj)) return obj.map((v) => unmarshalDates(v, keys));
152
153
  if (obj && typeof obj === "object") {
153
154
  const result = {};
@@ -169,8 +170,9 @@ const unmarshalDates = (obj, keys) => {
169
170
  *
170
171
  * @internal
171
172
  */
172
- const unmarshalAnyRes = (obj, ignoreKeys = [], dateKeys) => {
173
+ var unmarshalAnyRes = (obj, ignoreKeys = [], dateKeys) => {
173
174
  if (!isJSONObject(obj)) throw new TypeError(`Data isn't a dictionary.`);
174
175
  return camelizeKeys(dateKeys && dateKeys.length > 0 ? unmarshalDates(obj, dateKeys) : obj, ignoreKeys);
175
176
  };
176
- export { marshalBlobToScwFile, marshalDecimal, marshalMoney, marshalScwFile, marshalTimeSeries, unmarshalAnyRes, unmarshalDecimal, unmarshalMoney, unmarshalScwFile, unmarshalServiceInfo, unmarshalTimeSeries, unmarshalTimeSeriesPoint };
177
+ //#endregion
178
+ export { marshalBlobToScwFile, marshalDecimal, marshalMoney, marshalScwFile, marshalTimeSeries, marshalTimeSeriesPoint, unmarshalAnyRes, unmarshalDates, unmarshalDecimal, unmarshalMoney, unmarshalScwFile, unmarshalServiceInfo, unmarshalTimeSeries, unmarshalTimeSeriesPoint };
@@ -1,3 +1,4 @@
1
+ //#region src/scw/custom-types.ts
1
2
  /** A representation of a decimal value, such as 2.5.
2
3
  * Comparable to language-native decimal formats, such as Java's BigDecimal or Python's decimal.Decimal.
3
4
  * Lookup protobuf google.type.Decimal for details */
@@ -9,4 +10,5 @@ var Decimal = class {
9
10
  toString = () => this.str;
10
11
  marshal = () => ({ value: this.str });
11
12
  };
13
+ //#endregion
12
14
  export { Decimal };
@@ -0,0 +1,41 @@
1
+ import { ScalewayError } from "../scw-error.js";
2
+ import { describe, expect, it } from "vitest";
3
+ //#region src/scw/errors/__tests__/scw-error.test.ts
4
+ describe("ScalewayError", () => {
5
+ it("initializes from JSON", () => {
6
+ expect(ScalewayError.fromJSON(400, {})?.message).toBe("http error 400");
7
+ });
8
+ it("returns proper name, even if class name has changed", () => {
9
+ class AltError extends ScalewayError {}
10
+ expect(new AltError(200, "").name).toBe("ScalewayError");
11
+ });
12
+ it(`returns only the status code for empty body`, () => {
13
+ expect(new ScalewayError(400, {}).message).toBe(`http error 400`);
14
+ });
15
+ it(`handles a string body`, () => {
16
+ expect(new ScalewayError(400, "unknown error").message).toBe(`http error 400: unknown error`);
17
+ });
18
+ it(`handles a non-JSON object`, () => {
19
+ expect(new ScalewayError(400, []).message).toBe(`http error 400`);
20
+ });
21
+ it(`handles a resource field`, () => {
22
+ expect(new ScalewayError(400, { resource: "registry" }).message).toBe(`http error 400: resource registry`);
23
+ });
24
+ it(`handles a message field`, () => {
25
+ expect(new ScalewayError(400, { message: "plain text message" }).message).toBe(`http error 400: plain text message`);
26
+ expect(new ScalewayError(400, { message: "plain text message" }).rawMessage).toBe(`plain text message`);
27
+ });
28
+ it(`handles 1 field with 1 message`, () => {
29
+ expect(new ScalewayError(400, { fields: { project_id: ["value is required"] } }).message).toBe(`http error 400: project_id (value is required)`);
30
+ });
31
+ it(`handles 1 field with 2 messages`, () => {
32
+ expect(new ScalewayError(400, { fields: { project_id: ["value is required", "incorrect value"] } }).message).toBe(`http error 400: project_id (value is required, incorrect value)`);
33
+ });
34
+ it(`handles 2 fields`, () => {
35
+ expect(new ScalewayError(400, { fields: {
36
+ organization_id: ["project_id is already specified"],
37
+ project_id: ["value is required"]
38
+ } }).message).toBe(`http error 400: organization_id (project_id is already specified), project_id (value is required)`);
39
+ });
40
+ });
41
+ //#endregion
@@ -0,0 +1,16 @@
1
+ import { isRecordOfStringArray } from "../types.js";
2
+ import { describe, expect, it } from "vitest";
3
+ //#region src/scw/errors/__tests__/types.test.ts
4
+ describe("isRecordOfStringArray", () => {
5
+ it("validates a proper object", () => {
6
+ expect(isRecordOfStringArray({ myValue: ["reason 1", "reason 2"] })).toBeTruthy();
7
+ });
8
+ it("refuses object with proper structure but non-string values", () => {
9
+ expect(isRecordOfStringArray({ myValue: [42] })).toBeFalsy();
10
+ });
11
+ it("refuses non-JSONObjects", () => {
12
+ expect(isRecordOfStringArray(["reason 1"])).toBeFalsy();
13
+ expect(isRecordOfStringArray("reason 1")).toBeFalsy();
14
+ });
15
+ });
16
+ //#endregion
@@ -13,6 +13,7 @@ import { ResourceExpiredError } from "./standard/resource-expired-error.js";
13
13
  import { ResourceLockedError } from "./standard/resource-locked-error.js";
14
14
  import { TooManyRequestsError } from "./standard/too-many-requests-error.js";
15
15
  import { TransientStateError } from "./standard/transient-state-error.js";
16
+ //#region src/scw/errors/error-parser.ts
16
17
  /**
17
18
  * Unmarshals a standard error from raw body.
18
19
  *
@@ -92,7 +93,8 @@ var unmarshalNonStandardError = (type, status, body) => {
92
93
  *
93
94
  * @internal
94
95
  */
95
- const parseScalewayError = (status, body) => {
96
+ var parseScalewayError = (status, body) => {
96
97
  return typeof body.type === "string" && (unmarshalStandardError(body.type, status, body) ?? unmarshalNonStandardError(body.type, status, body)) || new ScalewayError(status, body);
97
98
  };
99
+ //#endregion
98
100
  export { parseScalewayError };
@@ -0,0 +1,123 @@
1
+ import { ScalewayError } from "../../scw-error.js";
2
+ import { InvalidArgumentsError } from "../../standard/invalid-arguments-error.js";
3
+ import { QuotasExceededError } from "../../standard/quotas-exceeded-error.js";
4
+ import { ResourceNotFoundError } from "../../standard/resource-not-found-error.js";
5
+ import { parseScalewayError } from "../../error-parser.js";
6
+ import { describe, expect, it } from "vitest";
7
+ //#region src/scw/errors/non-standard/__tests__/index.test.ts
8
+ describe("InvalidRequestError", () => {
9
+ it(`transforms to InvalidArgumentsError when there are fields`, () => {
10
+ const error = parseScalewayError(400, {
11
+ fields: {
12
+ "volumes.5.id": ["92 is not a valid UUID."],
13
+ "volumes.5.name": ["required key not provided"]
14
+ },
15
+ message: "Validation Error",
16
+ type: "invalid_request_error"
17
+ });
18
+ expect(error).toBeInstanceOf(InvalidArgumentsError);
19
+ expect(error instanceof Error ? error.details : void 0).toEqual([{
20
+ argumentName: "volumes.5.id",
21
+ helpMessage: "92 is not a valid UUID.",
22
+ reason: "constraint"
23
+ }, {
24
+ argumentName: "volumes.5.name",
25
+ helpMessage: "required key not provided",
26
+ reason: "constraint"
27
+ }]);
28
+ expect(error instanceof Error ? error.message : String(error)).toBe("invalid argument(s): volumes.5.id does not respect constraint, 92 is not a valid UUID.; volumes.5.name does not respect constraint, required key not provided");
29
+ });
30
+ it(`transforms to QuotasExceededError when message includes a specific text`, () => {
31
+ const error = parseScalewayError(400, {
32
+ message: "Quota exceeded for this resource.",
33
+ resource: "compute_snapshots_type_b_ssd_available",
34
+ type: "invalid_request_error"
35
+ });
36
+ expect(error).toBeInstanceOf(QuotasExceededError);
37
+ expect(error instanceof Error ? error.list : void 0).toEqual([{
38
+ current: 0,
39
+ quota: 0,
40
+ resource: "compute_snapshots_type_b_ssd_available"
41
+ }]);
42
+ expect(error instanceof Error ? error.message : String(error)).toBe("quota(s) exceeded: Quotas reached: You have reached the maximum number of compute_snapshots_type_b_ssd_available authorized by your Organization. Access the quotas page from your Organization dashboard to manage quotas.");
43
+ });
44
+ it(`transforms to QuotasExceededError when message includes a specific text, no resource case`, () => {
45
+ const error = parseScalewayError(400, {
46
+ message: "Quota exceeded for this resource.",
47
+ type: "invalid_request_error"
48
+ });
49
+ expect(error).toBeInstanceOf(QuotasExceededError);
50
+ expect(error instanceof Error ? error.list : void 0).toEqual([{
51
+ current: 0,
52
+ quota: 0,
53
+ resource: ""
54
+ }]);
55
+ });
56
+ it(`fallbacks to ScalewayError when there is only a message`, () => {
57
+ const error = parseScalewayError(400, {
58
+ message: "server should be running",
59
+ type: "invalid_request_error"
60
+ });
61
+ expect(error).toBeInstanceOf(ScalewayError);
62
+ expect(error instanceof Error ? error.message : String(error)).toBe("http error 400: server should be running");
63
+ });
64
+ it(`fallbacks on ScalewayError without a message`, () => {
65
+ const error = parseScalewayError(400, { type: "invalid_request_error" });
66
+ expect(error).toBeInstanceOf(ScalewayError);
67
+ expect(error instanceof Error ? error.message : String(error)).toBe("http error 400");
68
+ });
69
+ });
70
+ describe("UnknownResourceError", () => {
71
+ it(`transforms to ResourceNotFoundError with only the resource ID`, () => {
72
+ const error = parseScalewayError(404, {
73
+ message: `"11111111-1111-4111-8111-111111111142" not found`,
74
+ type: "unknown_resource"
75
+ });
76
+ expect(error).toBeInstanceOf(ResourceNotFoundError);
77
+ expect(error instanceof Error ? error.message : String(error)).toBe("resource with ID 11111111-1111-4111-8111-111111111142 is not found");
78
+ expect(error instanceof Error ? error.resource : void 0).toBe("");
79
+ expect(error instanceof Error ? error.resourceId : void 0).toBe("11111111-1111-4111-8111-111111111142");
80
+ });
81
+ it(`transforms to ResourceNotFoundError with the resource Name and ID`, () => {
82
+ const error = parseScalewayError(404, {
83
+ message: `Security group "11111111-1111-4111-8111-111111111112" not found`,
84
+ type: "unknown_resource"
85
+ });
86
+ expect(error).toBeInstanceOf(ResourceNotFoundError);
87
+ expect(error instanceof Error ? error.message : String(error)).toBe("resource security_group with ID 11111111-1111-4111-8111-111111111112 is not found");
88
+ expect(error instanceof Error ? error.resource : void 0).toBe("security_group");
89
+ expect(error instanceof Error ? error.resourceId : void 0).toBe("11111111-1111-4111-8111-111111111112");
90
+ });
91
+ it("transforms to ResourceNotFoundError with the not found resource / single quote", () => {
92
+ const error = parseScalewayError(404, {
93
+ message: `Volume '11111111-1111-4111-8111-111111111111' not found`,
94
+ type: "unknown_resource"
95
+ });
96
+ expect(error).toBeInstanceOf(ResourceNotFoundError);
97
+ expect(error instanceof Error ? error.message : String(error)).toBe("resource volume with ID 11111111-1111-4111-8111-111111111111 is not found");
98
+ expect(error instanceof Error ? error.resource : void 0).toBe("volume");
99
+ expect(error instanceof Error ? error.resourceId : void 0).toBe("11111111-1111-4111-8111-111111111111");
100
+ });
101
+ it(`transforms to ScalewayError when the message can't be analyzed`, () => {
102
+ const error = parseScalewayError(404, {
103
+ message: "uncommon message",
104
+ type: "unknown_resource"
105
+ });
106
+ expect(error).toBeInstanceOf(ScalewayError);
107
+ expect(error instanceof Error ? error.message : String(error)).toBe("http error 404: uncommon message");
108
+ });
109
+ it("do not transform to ResourceNotFoundError for invalid resource ID", () => {
110
+ const error = parseScalewayError(404, {
111
+ message: `Volume 'not-an-uuid' not found`,
112
+ type: "unknown_resource"
113
+ });
114
+ expect(error).not.toBeInstanceOf(ResourceNotFoundError);
115
+ expect(error instanceof Error ? error.message : String(error)).toBe(`http error 404: Volume 'not-an-uuid' not found`);
116
+ });
117
+ it("fallbacks to ScalewayError without a message", () => {
118
+ const error = parseScalewayError(404, { type: "unknown_resource" });
119
+ expect(error).toBeInstanceOf(ScalewayError);
120
+ expect(error instanceof Error ? error.message : String(error)).toBe("http error 404");
121
+ });
122
+ });
123
+ //#endregion
@@ -2,12 +2,13 @@ import { isRecordOfStringArray } from "../types.js";
2
2
  import { ScalewayError } from "../scw-error.js";
3
3
  import { InvalidArgumentsError } from "../standard/invalid-arguments-error.js";
4
4
  import { QuotasExceededError } from "../standard/quotas-exceeded-error.js";
5
+ //#region src/scw/errors/non-standard/invalid-request-mapper.ts
5
6
  /**
6
7
  * InvalidRequest error is only returned by the instance API.
7
8
  *
8
9
  * @public
9
10
  */
10
- const mapInvalidRequestFromJSON = (status, obj) => {
11
+ var mapInvalidRequestFromJSON = (status, obj) => {
11
12
  if (typeof obj.message === "string" && obj.message.toLowerCase().includes("quota exceeded for this resource")) return new QuotasExceededError(status, obj, [{
12
13
  current: 0,
13
14
  quota: 0,
@@ -22,4 +23,5 @@ const mapInvalidRequestFromJSON = (status, obj) => {
22
23
  }))));
23
24
  return new ScalewayError(status, obj);
24
25
  };
26
+ //#endregion
25
27
  export { mapInvalidRequestFromJSON };
@@ -1,14 +1,16 @@
1
1
  import { isUUID } from "../../../internal/validations/string-validation.js";
2
2
  import { ScalewayError } from "../scw-error.js";
3
3
  import { ResourceNotFoundError } from "../standard/resource-not-found-error.js";
4
+ //#region src/scw/errors/non-standard/unknown-resource-mapper.ts
4
5
  /**
5
6
  * UnknownResource error is only returned by the instance API.
6
7
  *
7
8
  * @public
8
9
  */
9
- const mapUnknownResourceFromJSON = (status, obj) => {
10
+ var mapUnknownResourceFromJSON = (status, obj) => {
10
11
  const messageParts = typeof obj.message === "string" ? obj.message.split(/"|'/) : [];
11
12
  if (messageParts.length === 3 && isUUID(messageParts[1])) return new ResourceNotFoundError(status, obj, messageParts[0].trim().toLowerCase().split(" ").join("_"), messageParts[1]);
12
13
  return new ScalewayError(status, obj);
13
14
  };
15
+ //#endregion
14
16
  export { mapUnknownResourceFromJSON };
File without changes
@@ -1,5 +1,6 @@
1
1
  import { isJSONObject } from "../../helpers/json.js";
2
2
  import { isRecordOfStringArray } from "./types.js";
3
+ //#region src/scw/errors/scw-error.ts
3
4
  /**
4
5
  * Builds the default message for {@link ScalewayError}.
5
6
  *
@@ -43,4 +44,5 @@ var ScalewayError = class ScalewayError extends Error {
43
44
  return `${this.name}: ${this.message}`;
44
45
  }
45
46
  };
47
+ //#endregion
46
48
  export { ScalewayError };