Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New blocktype HttpExtractor (UAC-09 of #123) #134

Merged
merged 5 commits into from Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
92 changes: 92 additions & 0 deletions libs/extensions/std/exec/src/http-extractor-executor.ts
@@ -0,0 +1,92 @@
import * as https from 'https';

import * as R from '@jayvee/execution';
import { BlockExecutor } from '@jayvee/execution';
import { File, FileExtension, MimeType } from '@jayvee/language-server';

export class HttpExtractorExecutor extends BlockExecutor<void, File> {
constructor() {
super('HttpExtractor');
}

override async execute(): Promise<R.Result<File>> {
// Accessing attribute values by their name:
const url = this.getStringAttributeValue('url');

const file = await this.fetchRawDataAsFile(url);

if (R.isErr(file)) {
return file;
}

return R.ok(file.right);
}

private fetchRawDataAsFile(url: string): Promise<R.Result<File>> {
this.logger.logDebug(`Fetching raw data from ${url}`);
return new Promise((resolve) => {
https.get(url, (response) => {
const responseCode = response.statusCode;

// Catch errors
if (responseCode === undefined || responseCode >= 400) {
resolve(
R.err({
message: `HTTP fetch failed with code ${
responseCode ?? 'undefined'
}. Please check your connection.`,
diagnostic: { node: this.getOrFailAttribute('url') },
}),
);
}

// Get chunked data and store to ArrayBuffer
let rawData = new Uint8Array(0);
response.on('data', (chunk: Buffer) => {
const tmp = new Uint8Array(rawData.length + chunk.length);
tmp.set(rawData, 0);
tmp.set(chunk, rawData.length);
rawData = tmp;
});

// When all data is downloaded, create file
response.on('end', () => {
this.logger.logDebug(`Successfully fetched raw data`);
response.headers;

// Infer Mimetype from HTTP-Header, if not inferrable, then default to application/octet-stream
let inferredMimeType = response.headers['content-type'] as MimeType;
schlingling marked this conversation as resolved.
Show resolved Hide resolved
if (!Object.values(MimeType).includes(inferredMimeType)) {
inferredMimeType = MimeType.APPLICATION_OCTET_STREAM;
}

// Infer FileExtension from user input, if not inferrable, then default to None
let inferredFileExtension = this.getStringAttributeValue(
'fileExtension',
) as FileExtension;
if (!Object.values(MimeType).includes(inferredMimeType)) {
inferredFileExtension = FileExtension.NONE;
}

// Create file and return file
const file: File = {
name: this.getStringAttributeValue('fileName'),
extension: inferredFileExtension,
content: rawData.buffer as ArrayBuffer,
mimeType: inferredMimeType,
};
resolve(R.ok(file));
});

response.on('error', (errorObj) => {
resolve(
R.err({
message: errorObj.message,
diagnostic: { node: this.block, property: 'name' },
}),
);
});
});
});
}
}
34 changes: 34 additions & 0 deletions libs/extensions/std/lang/src/http-extractor-meta-inf.ts
@@ -0,0 +1,34 @@
import {
AttributeType,
BlockMetaInformation,
FILE_TYPE,
UNDEFINED_TYPE,
} from '@jayvee/language-server';

export class HttpExtractorMetaInformation extends BlockMetaInformation {
constructor() {
super(
// How the block type should be called:
'HttpExtractor',

// Input type:
UNDEFINED_TYPE,

// Output type:
FILE_TYPE,

// Attribute definitions:
{
url: {
type: AttributeType.STRING,
},
fileName: {
schlingling marked this conversation as resolved.
Show resolved Hide resolved
type: AttributeType.STRING,
},
fileExtension: {
type: AttributeType.STRING,
},
},
);
}
}
Expand Up @@ -39,6 +39,7 @@ export interface File {
export enum FileExtension {
ZIP = 'zip',
TXT = 'txt',
NONE = '',
}

/**
Expand Down
Expand Up @@ -2,7 +2,7 @@ import { TextEncoder } from 'util';

import { File, FileExtension, MimeType } from './file-io-type';
import { InMemoryFileSystem } from './filesystem-inmemory';
import { NONE_TYPE } from './none-io-type';
import { NONE, NONE_TYPE } from './none-io-type';

describe('InMemoryFileSystem', () => {
let fileSystem: InMemoryFileSystem;
Expand Down Expand Up @@ -36,7 +36,7 @@ describe('InMemoryFileSystem', () => {
};

fileSystem.putFile('folder1/file1.txt', file);
expect(fileSystem.getFile('folder2/file1.txt')).toBe(NONE_TYPE);
expect(fileSystem.getFile('folder2/file1.txt')).toBe(NONE);
});

it('should handle relative paths correctly', () => {
Expand Down
@@ -1,6 +1,6 @@
import { File } from './file-io-type';
import { FileSystem } from './filesystem-io-type';
import { NONE_TYPE, None } from './none-io-type';
import { NONE, NONE_TYPE, None } from './none-io-type';

export class InMemoryFileSystem implements FileSystem {
private static PATH_SEPARATOR = '/';
Expand Down Expand Up @@ -29,7 +29,7 @@ export class InMemoryFileSystem implements FileSystem {
return processedParts;
}

getFile(filePath: string): File | FileSystem | None {
getFile(filePath: string): File | None {
const processedParts = this.processPath(filePath);

// If we have a minimum valid path
Expand All @@ -47,7 +47,7 @@ export class InMemoryFileSystem implements FileSystem {

// If we dont find current path-part, stop methodcall
if (!childFileSystem) {
return NONE_TYPE;
return NONE;
}
currentFileSystemIndex = childFileSystem.fileSystemIndex;
}
Expand All @@ -56,7 +56,7 @@ export class InMemoryFileSystem implements FileSystem {
const file = currentFileSystemIndex.get(fileName);
return file ? file : NONE_TYPE;
}
return NONE_TYPE;
return NONE;
}

putFile(filePath: string, file: File): FileSystem {
Expand Down
Expand Up @@ -11,9 +11,9 @@ export interface FileSystem {
* Retrieves a file from the file system.
* @function getFile
* @param {string} filePath - The file path.
* @returns {File | None} - The file or None if the file does not exist.
* @returns {File | FileSystem | None} - The file or None if the file does not exist.
*/
getFile(filePath: string): File | FileSystem | None;
getFile(filePath: string): File | None;

/**
* Saves a file to the file system.
Expand Down
Expand Up @@ -3,3 +3,4 @@ import { IOType } from './io-type';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface None {}
export const NONE_TYPE = new IOType<None>();
export const NONE: None = {};