Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions models/backbeatRoutes/putMetadata.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ structure PutMetadataInput {

@httpHeader("X-Scal-Request-Uids")
RequestUids: String,


@httpHeader("x-scal-micro-version-id")
MicroVersionId: String,

@httpPayload
Body: Blob
}

structure PutMetadataOutput {
/// Version ID of the stored metadata
versionId: String
versionId: String,

@httpHeader("x-scal-cascade-loop-detected")
CascadeLoopDetected: Boolean
}
10 changes: 8 additions & 2 deletions models/backbeatRoutes/putdata.smithy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

To document errors:

/// Returned when a version of an object already exists
@error("client")
@httpError(409)
structure ConflictException {
    @required
    message: String
}

@http(method: "PUT", uri: "/_/backbeat/data/{Bucket}/{Key+}?v2")
@unsignedPayload
operation PutData {
    input: PutDataInput,
    output: PutDataOutput,
    errors: [ConflictException]
}

Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ structure PutDataInput {
@httpHeader("X-Scal-Request-Uids")
RequestUids: String,

@httpHeader("x-scal-micro-version-id")
MicroVersionId: String,

@httpPayload
@default("")
Body: StreamingBlob
Expand All @@ -45,7 +48,10 @@ structure PutDataOutput {

@httpHeader("x-amz-server-side-encryption-customer-algorithm")
SSECustomerAlgorithm: String,

@httpHeader("x-amz-server-side-encryption-aws-kms-key-id")
SSEKMSKeyId: String
SSEKMSKeyId: String,

@httpHeader("x-scal-cascade-loop-detected")
CascadeLoopDetected: Boolean
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@scality/cloudserverclient",
"version": "1.0.8",
"version": "1.0.9",
"engines": {
"node": ">=20"
},
Expand Down Expand Up @@ -56,8 +56,10 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.1009.0",
"@aws-sdk/middleware-expect-continue": "^3.972.8",
"JSONStream": "^1.3.5",
"fast-xml-parser": "^5.5.7"
"fast-xml-parser": "^5.5.7",
"uuid": "11"
},
"resolutions": {
"flatted": "^3.4.2"
Expand Down
43 changes: 43 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,51 @@
import { addExpectContinueMiddleware } from '@aws-sdk/middleware-expect-continue';
import { MiddlewareStack, RequestHandler } from '@smithy/types';
import { XMLParser } from 'fast-xml-parser';
import {
CloudserverBackbeatRoutesServiceException
} from '../build/smithy/cloudserverBackbeatRoutes/typescript-codegen';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithMiddlewareStack = { middlewareStack?: MiddlewareStack<any, any> };

/**
* Attach the AWS SDK Expect: 100-continue middleware to a single command.
*
* Use this on commands whose target route honors 100-continue server-side.
* Pass the client's requestHandler so the underlying middleware can skip
* the header when running on FetchHttpHandler.
*
* @param command - The command to attach the middleware to.
* @param requestHandler - The client's requestHandler, used by the AWS SDK
* middleware to detect FetchHttpHandler and skip the header in that case.
* @param expectContinueHeader - Controls when the header is set:
* - `true` (default): always set the header on body-carrying requests.
* - `false`: never set the header (middleware no-op).
* - `number`: only set the header when the body's Content-Length is
* greater than or equal to this threshold (in bytes). Useful to skip
* the handshake cost on small payloads.
*/
export function attachExpectContinueMiddleware<TCommand>(
command: TCommand & WithMiddlewareStack,
requestHandler?: RequestHandler<unknown, unknown>,
expectContinueHeader: boolean | number = true,
): TCommand {
if (!command.middlewareStack) {
throw new Error('Command does not have a middleware stack');
}

command.middlewareStack.add(
addExpectContinueMiddleware({
runtime: 'node',
requestHandler,
expectContinueHeader,
}),
{ step: 'build', name: 'expectContinue' },
);

return command;
}

/**
* Adds middleware to manually set the Content-Length header on a command.
*
Expand All @@ -20,14 +63,14 @@
return;
}

const commandWithMiddleware = command as any;

Check warning on line 66 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type
if (!commandWithMiddleware.middlewareStack) {
throw new Error('Command does not have a middleware stack');
}

commandWithMiddleware.middlewareStack.add(
(next: any) => async (args: any) => {

Check warning on line 72 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type

Check warning on line 72 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type
const request = args.request as any;

Check warning on line 73 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type
if (request?.headers && !request.headers['content-length']) {
request.headers['content-length'] = String(contentLength);
}
Expand All @@ -40,10 +83,10 @@
}

export function createCustomErrorMiddleware() {
return (next: any) => async (args: any) => {

Check warning on line 86 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type

Check warning on line 86 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type
try {
return await next(args);
} catch (error: any) {

Check warning on line 89 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type
const parseXmlError = (xml: string) => {
try {
const result = new XMLParser({}).parse(xml);
Expand All @@ -70,7 +113,7 @@
const xml = body?.toString() || '';
const errorInfo = parseXmlError(xml);

const xmlError: any = new CloudserverBackbeatRoutesServiceException({

Check warning on line 116 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type
name: errorInfo.code || error.name,
message: errorInfo.message || 'XML error response',
$fault: statusCode >= 500 ? 'server' : 'client',
Expand All @@ -90,7 +133,7 @@
const title = html.match(/<title[^>]*>([^<]+)<\/title>/i);
const message = title && title[1] || 'HTML error response';

const htmlError: any = new CloudserverBackbeatRoutesServiceException({

Check warning on line 136 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type
name: `HTML ${response?.reason || 'Error'}`,
message,
$fault: statusCode >= 500 ? 'server' : 'client',
Expand Down
39 changes: 37 additions & 2 deletions tests/testApis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
} from '../src/index';
import { S3Client, GetObjectCommand as S3getCommand } from '@aws-sdk/client-s3';
import { createTestClient, testConfig } from './testSetup';
import { describeForMetadataBackend } from './testHelpers';

Check failure on line 16 in tests/testApis.test.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'describeForMetadataBackend' is defined but never used. Allowed unused vars must match /^_/u
import assert from 'assert';
import { v7 as uuidv7 } from 'uuid';

describeForMetadataBackend('CloudServer Backbeat Routes API Tests', () => {
describe('CloudServer Backbeat Routes API Tests', () => {
let backbeatRoutesClient: BackbeatRoutesClient;
let s3client: S3Client;

Expand All @@ -37,7 +38,7 @@
CanonicalID: testConfig.canonicalID,
ContentMD5: etag,
Body: getData.Body,
VersioningRequired: true
VersioningRequired: true,
};

const command2 = new PutDataCommand(putInput);
Expand All @@ -46,10 +47,44 @@
getData.ContentLength
);
const data = await backbeatRoutesClient.send(command2);
const locationAny: any = data.Location as any;

Check warning on line 50 in tests/testApis.test.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected any. Specify a different type
assert.ok(locationAny[0].key !== undefined);
});

it('should test putData for CRR-Cascaded', async () => {
const command = new S3getCommand({
Bucket: testConfig.bucketName,
Key: testConfig.objectKey,
});
const getData = await s3client.send(command);
const etag = getData.ETag?.replace(/"/g, '') || '';
let older = uuidv7();

Check failure on line 61 in tests/testApis.test.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'older' is never reassigned. Use 'const' instead
// sleep for 10ms
await new Promise(resolve => setTimeout(resolve, 1000));
const putInput: PutDataCommandInput = {
Bucket: testConfig.bucketName,
Key: testConfig.objectKey,
CanonicalID: testConfig.canonicalID,
ContentMD5: etag,
Body: getData.Body,
VersioningRequired: true,
MicroVersionId: uuidv7(),
};

const command2 = new PutDataCommand(putInput);
addContentLengthMiddleware(
command2,
getData.ContentLength
);
const data = await backbeatRoutesClient.send(command2);
console.log("AAAA data: ", data);

Check failure on line 80 in tests/testApis.test.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected console statement
command2.input.MicroVersionId = older;
const data2 = await backbeatRoutesClient.send(command2);
console.log("AAAA data: ", data2);

Check failure on line 83 in tests/testApis.test.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected console statement
// const locationAny: any = data.Location as any;
// assert.ok(locationAny[0].key !== undefined);
});

it('should test GetSingleObject', async () => {
const getInput: GetObjectInput = {
Bucket: testConfig.bucketName,
Expand Down
Loading
Loading