All files / src/Infrastructure/ExternalService/Translator HttpClient.ts

92.59% Statements 25/27
90% Branches 9/10
100% Functions 2/2
92.59% Lines 25/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108    2x 2x   2x 2x         2x 5x   5x                 7x   7x 1x         1x           6x   6x 6x                                       6x 1x             1x           5x   5x 5x 1x             1x         4x       4x           4x              
import { ClientInterface } from '@Application/Shared/Translator/ClientInterface';
import { Result } from '@Domain/Types/Result';
import { HttpError } from '@Domain/Errors/HttpError';
import { ValidationError } from '@Domain/Errors/ValidationError';
import { LoggerInterface } from '@Application/Shared/Monitoring/LoggerInterface';
import { TranslationResponseSchema } from './types';
import config from '@Infrastructure/Environments/config';
 
/**
* HTTP client for communicating with the Translation API
*/
export class HttpClient implements ClientInterface {
    private readonly baseUrl = config.translatorClient.baseUrl;
 
    constructor(private readonly logger: LoggerInterface) { }
 
    /**
     * Retrieves a translation for the given text
     * @param translationType The type of translation to use (e.g., 'yoda', 'shakespeare')
     * @param text The text to translate
     * @returns A promise resolving to a Result containing either the translated text or an Error
     */
    async getTranslation(translationType: string, text: string): Promise<Result<Error, string>> {
        this.logger.info(`Fetching translation`, { translationType, text });
 
        if (!text || !translationType) {
            this.logger.error(`Text or translation type are required`, {
                translationType,
                text,
            });
 
            return {
                success: false,
                error: new ValidationError('Text or translation type are required'),
            };
        }
 
        const body = new URLSearchParams({ text });
        let response: Response;
        try {
            response = await fetch(`${this.baseUrl}/${translationType}.json`, {
                method: 'POST',
                headers: {
                    ...(config.translatorClient.apiKey.length > 0 ? { 'X-Funtranslations-Api-Secret': config.translatorClient.apiKey } : {}),
                },
                body: body,
            });
        } catch (error) {
            this.logger.error(`Failed to fetch translation`, {
                translationType,
                text,
                body: error,
            });
 
            return {
                success: false,
                error: new HttpError(500, `Failed to fetch '${translationType}' translation for '${text}'`),
            };
        }
 
        if (!response.ok) {
            this.logger.error(`Failed to fetch translation`, {
                translationType,
                text,
                status: response.status,
                body: await response.text(),
            });
 
            return {
                success: false,
                error: new HttpError(response.status, `Failed to fetch '${translationType}' translation for '${text}'`),
            };
        }
 
        const data = await response.json();
 
        const result = TranslationResponseSchema.safeParse(data);
        if (!result.success) {
            this.logger.error(`Failed to parse translation`, {
                translationType,
                text,
                body: data,
                error: result.error.message,
            });
 
            return {
                success: false,
                error: new ValidationError(result.error.message),
            };
        } else {
            this.logger.info(`Translation fetched successfully`, {
                translationType,
                text,
            });
            this.logger.debug(`Translation fetched successfully: body`, {
                translationType,
                text,
                body: data,
            });
 
            return {
                success: true,
                data: result.data.contents.translated,
            };
        }
    }
}