Skip to content

Commit

Permalink
[IO-363] add Accept and ContentType headers
Browse files Browse the repository at this point in the history
  • Loading branch information
Geksanit committed Jun 7, 2021
1 parent 578de11 commit f4d907e
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 81 deletions.
52 changes: 30 additions & 22 deletions src/language/typescript/3.0/serializers/operation-object.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
DEFAULT_MEDIA_TYPE,
getJSDoc,
getKindValue,
getResponseTypeFromMediaType,
getSafePropertyName,
getTypeName,
getURL,
Expand Down Expand Up @@ -38,12 +40,12 @@ import {
} from '../../common/data/serialized-path-parameter';
import { concatIf } from '../../../../utils/array';
import { when } from '../../../../utils/string';
import { serializeRequestBodyObject } from './request-body-object';
import { getRequestMedia, serializeRequestBodyObject } from './request-body-object';
import { ResolveRefContext, fromString, getRelativePath, Ref } from '../../../../utils/ref';
import { OperationObject } from '../../../../schema/3.0/operation-object';
import { ParameterObject, ParameterObjectCodec } from '../../../../schema/3.0/parameter-object';
import { RequestBodyObjectCodec } from '../../../../schema/3.0/request-body-object';
import { chain, isSome, none, Option, some, map, fromEither, fold } from 'fp-ts/lib/Option';
import { chain, isSome, none, Option, some, map, fromEither, getOrElse, fold } from 'fp-ts/lib/Option';
import { constFalse } from 'fp-ts/lib/function';
import { clientRef } from '../../common/bundled/client';
import { Kind } from '../../../../utils/types';
Expand All @@ -58,13 +60,14 @@ import {
SerializedFragment,
} from '../../common/data/serialized-fragment';
import { SchemaObjectCodec } from '../../../../schema/3.0/schema-object';
import { lookup, keys } from 'fp-ts/lib/Record';
import { lookup } from 'fp-ts/lib/Record';
import { ResponseObjectCodec } from '../../../../schema/3.0/response-object';
import {
fromSerializedHeaderParameter,
getSerializedHeaderParameterType,
SerializedHeaderParameter,
} from '../../common/data/serialized-header-parameters';
import { getResponseMedia } from './response-object';

const getOperationName = (pattern: string, operation: OperationObject, method: HTTPMethod): string =>
pipe(
Expand Down Expand Up @@ -266,9 +269,6 @@ export const getParameters = combineReader(
},
);

const blobMediaRegexp = /^(video|audio|image|application)/;
const textMediaRegexp = /^text/;

export const serializeOperationObject = combineReader(
ask<ResolveRefContext>(),
getParameters,
Expand All @@ -289,7 +289,7 @@ export const serializeOperationObject = combineReader(
);

const serializedResponses = serializeResponsesObject(from)(operation.responses);
const responseType: XHRResponseType = pipe(
const mediaType: string = pipe(
SUCCESSFUL_CODES,
array.findFirstMap(code => lookup(code, operation.responses)),
chain(response =>
Expand All @@ -298,23 +298,30 @@ export const serializeOperationObject = combineReader(
: some(response),
),
chain(response => response.content),
map(keys),
chain(getResponseMedia),
map(({ key }) => key),
getOrElse(() => DEFAULT_MEDIA_TYPE),
);
const responseType: XHRResponseType = getResponseTypeFromMediaType(mediaType);
const serializedContentType = pipe(
operation.requestBody,
chain(requestBody =>
ReferenceObjectCodec.is(requestBody)
? fromEither(e.resolveRef(requestBody.$ref, RequestBodyObjectCodec))
: some(requestBody),
),
map(request => request.content),
chain(getRequestMedia),
map(({ key }) => key),
fold(
() => 'json',
types => {
if (types.includes('application/json')) {
return 'json';
}
if (types.some(s => blobMediaRegexp.test(s))) {
return 'blob';
}
if (types.some(s => textMediaRegexp.test(s))) {
return 'text';
}
return 'json';
},
() => '',
contentType => `'Content-type': '${contentType}',`,
),
);
const requestHeaders = `{
Accept: '${mediaType}',
${serializedContentType}
}`;

return combineEither(
parameters,
Expand Down Expand Up @@ -382,6 +389,7 @@ export const serializeOperationObject = combineReader(
${bodyIO}
${queryIO}
${headersIO}
const requestHeaders = ${requestHeaders}
return e.httpClient.chain(
e.httpClient.request({
Expand All @@ -390,7 +398,7 @@ export const serializeOperationObject = combineReader(
responseType: '${responseType}',
${when(hasQueryParameters, 'query,')}
${when(hasBodyParameter, 'body,')}
${when(hasHeaderParameters, 'headers')}
headers: {${hasHeaderParameters ? '...headers,' : ''} ...requestHeaders}
}),
value =>
pipe(
Expand Down
60 changes: 42 additions & 18 deletions src/language/typescript/3.0/serializers/request-body-object.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,59 @@
import { serializeSchemaObject } from './schema-object';
import { getSerializedRefType, SerializedType } from '../../common/data/serialized-type';
import {
getSerializedBlobType,
getSerializedRefType,
SerializedType,
SERIALIZED_STRING_TYPE,
} from '../../common/data/serialized-type';
import { Either, mapLeft } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { either, option } from 'fp-ts';
import { fromString, Ref } from '../../../../utils/ref';
import { RequestBodyObject } from '../../../../schema/3.0/request-body-object';
import { ReferenceObjectCodec, ReferenceObject } from '../../../../schema/3.0/reference-object';
import { SchemaObject } from '../../../../schema/3.0/schema-object';
import { getKeyMatchValue } from '../../common/utils';
import { getKeyMatchValue, getResponseTypeFromMediaType, XHRResponseType } from '../../common/utils';
import { MediaTypeObject } from '../../../../schema/3.0/media-type-object';

const requestMediaRegexp = /^(video|audio|image|application|text|multipart\/form-data)/;
export const getRequestMedia = (content: Record<string, MediaTypeObject>) =>
getKeyMatchValue(content, requestMediaRegexp);

export const serializeRequestBodyObject = (from: Ref, body: RequestBodyObject): Either<Error, SerializedType> =>
pipe(
getSchema(body),
either.chain(schema =>
ReferenceObjectCodec.is(schema)
getRequestMedia(body.content),
option.chain(({ key: mediaType, value: { schema } }) =>
pipe(
schema,
option.map(schema => ({ mediaType, schema })),
),
),
either.fromOption(() => new Error('No schema found for ReqeustBodyObject')),
either.chain(({ mediaType, schema }) => {
const resType = getResponseTypeFromMediaType(mediaType);
return serializeRequestSchema(resType, schema, from);
}),
);

const serializeRequestSchema = (
responseType: XHRResponseType,
schema: ReferenceObject | SchemaObject,
from: Ref,
): Either<Error, SerializedType> => {
switch (responseType) {
case 'json':
return ReferenceObjectCodec.is(schema)
? pipe(
schema.$ref,
fromString,
fromString(schema.$ref),
mapLeft(
() => new Error(`Invalid MediaObject.content.$ref "${schema.$ref}" for RequestBodyObject`),
),
either.map(getSerializedRefType(from)),
)
: serializeSchemaObject(from)(schema),
),
);

const requestMediaRegexp = /^(video|audio|image|application|text|multipart\/form-data)/;
const getSchema = (requestBodyObject: RequestBodyObject): Either<Error, ReferenceObject | SchemaObject> =>
pipe(
getKeyMatchValue(requestBodyObject.content, requestMediaRegexp),
option.chain(media => media.schema),
either.fromOption(() => new Error('No schema found for ReqeustBodyObject')),
);
: serializeSchemaObject(from)(schema);
case 'text':
return either.right(SERIALIZED_STRING_TYPE);
case 'blob':
return getSerializedBlobType(from);
}
};
48 changes: 39 additions & 9 deletions src/language/typescript/3.0/serializers/response-object.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,57 @@
import { SerializedType, getSerializedRefType } from '../../common/data/serialized-type';
import {
SerializedType,
getSerializedRefType,
SERIALIZED_STRING_TYPE,
getSerializedBlobType,
} from '../../common/data/serialized-type';
import { pipe } from 'fp-ts/lib/pipeable';
import { serializeSchemaObject } from './schema-object';
import { Either } from 'fp-ts/lib/Either';
import { fromString, Ref } from '../../../../utils/ref';
import { either, option } from 'fp-ts';
import { ResponseObject } from '../../../../schema/3.0/response-object';
import { Option } from 'fp-ts/lib/Option';
import { ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
import { getKeyMatchValue } from '../../common/utils';
import { ReferenceObject, ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
import { getKeyMatchValue, getResponseTypeFromMediaType, XHRResponseType } from '../../common/utils';
import { SchemaObject } from '../../../../schema/3.0/schema-object';
import { MediaTypeObject } from '../../../../schema/3.0/media-type-object';

const requestMediaRegexp = /^(video|audio|image|application|text)/;
export const getResponseMedia = (content: Record<string, MediaTypeObject>) =>
getKeyMatchValue(content, requestMediaRegexp);

export const serializeResponseObject = (
from: Ref,
responseObject: ResponseObject,
): Option<Either<Error, SerializedType>> =>
pipe(
responseObject.content,
option.chain(content => getKeyMatchValue(content, requestMediaRegexp)),
option.chain(media => media.schema),
option.map(schema =>
ReferenceObjectCodec.is(schema)
? pipe(fromString(schema.$ref), either.map(getSerializedRefType(from)))
: serializeSchemaObject(from)(schema),
option.chain(content => getResponseMedia(content)),
option.chain(({ key: mediaType, value: { schema } }) =>
pipe(
schema,
option.map(schema => ({ mediaType, schema })),
),
),
option.map(({ mediaType, schema }) => {
const resType = getResponseTypeFromMediaType(mediaType);
return serializeResponseSchema(resType, schema, from);
}),
);

const serializeResponseSchema = (
responseType: XHRResponseType,
schema: ReferenceObject | SchemaObject,
from: Ref,
): Either<Error, SerializedType> => {
switch (responseType) {
case 'json':
return ReferenceObjectCodec.is(schema)
? pipe(fromString(schema.$ref), either.map(getSerializedRefType(from)))
: serializeSchemaObject(from)(schema);
case 'text':
return either.right(SERIALIZED_STRING_TYPE);
case 'blob':
return getSerializedBlobType(from);
}
};
25 changes: 23 additions & 2 deletions src/language/typescript/common/bundled/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ import { fromRef } from '../../../../utils/fs';
export const utilsRef = fromString('#/utils/utils');

const utils = `
import { either } from 'fp-ts/lib/Either';
import { Type, type, TypeOf, failure, success, string as tstring, literal } from 'io-ts';
import { either, left, right } from 'fp-ts/lib/Either';
import {
Type,
type,
TypeOf,
failure,
success,
string as tstring,
literal,
Validate,
Context,
getValidationError,
} from 'io-ts';
export const DateFromISODateStringIO = new Type<Date, string, unknown>(
'DateFromISODateString',
Expand Down Expand Up @@ -53,6 +64,16 @@ const utils = `
a => a.string,
);
const validateBlob: Validate<unknown, Blob> = (u: unknown, c: Context) =>
u instanceof Blob ? right(u) : left([getValidationError(u, c)]);
export const BlobToBlobIO = new Type<Blob, Blob, unknown>(
'Base64FromString',
(u): u is Blob => u instanceof Blob,
validateBlob,
a => a,
);
`;

export const utilsFile = pipe(
Expand Down
11 changes: 11 additions & 0 deletions src/language/typescript/common/data/serialized-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ export const SERIALIZED_UNKNOWN_TYPE = serializedType(
[serializedDependency('unknown', 'io-ts')],
[],
);
export const getSerializedBlobType = (from: Ref): Either<Error, SerializedType> => {
return combineEither(utilsRef, utilsRef =>
serializedType(
'Blob',
'BlobToBlobIO',
[serializedDependency('BlobToBlobIO', getRelativePath(from, utilsRef))],
[],
),
);
};
export const SERIALIZED_BOOLEAN_TYPE = serializedType(
'boolean',
'boolean',
Expand Down Expand Up @@ -109,6 +119,7 @@ export const getSerializedStringType = (from: Ref, format: Option<string>): Eith
),
);
}
case 'byte':
case 'base64': {
return some(
serializedType(
Expand Down
20 changes: 18 additions & 2 deletions src/language/typescript/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,26 @@ export const getSafePropertyName = (value: string): string =>

export const context = ask<ResolveRefContext>();

export const getKeyMatchValue = <T>(record: Record<string, T>, regexp: RegExp): option.Option<T> =>
export const getKeyMatchValue = <T extends string, A>(record: Record<T, A>, regexp: RegExp) =>
pipe(
record,
keys,
array.findFirst(s => regexp.test(s)),
option.map(key => record[key]),
option.map(key => ({ key, value: record[key] })),
);

const blobMediaRegexp = /^(video|audio|image|application)/;
const textMediaRegexp = /^text/;
export const DEFAULT_MEDIA_TYPE = 'application/json';
export const getResponseTypeFromMediaType = (mediaType: string): XHRResponseType => {
if (mediaType === 'application/json') {
return 'json';
}
if (blobMediaRegexp.test(mediaType)) {
return 'blob';
}
if (textMediaRegexp.test(mediaType)) {
return 'text';
}
return 'json';
};
Loading

0 comments on commit f4d907e

Please sign in to comment.