Skip to content

Commit

Permalink
Merge pull request #129 from mankdev/response-type-support
Browse files Browse the repository at this point in the history
Support binary and text responses
  • Loading branch information
dramoturg authored Apr 1, 2021
2 parents 24437cd + 855c61d commit 977f534
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 8 deletions.
23 changes: 20 additions & 3 deletions src/language/typescript/2.0/serializers/operation-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
SerializedType,
} from '../../common/data/serialized-type';
import { pipe } from 'fp-ts/lib/pipeable';
import { getOrElse, isSome, map, Option } from 'fp-ts/lib/Option';
import { getOrElse, isSome, map, Option, fold } from 'fp-ts/lib/Option';
import { serializeOperationResponses } from './responses-object';
import { fromSerializedType } from '../../common/data/serialized-parameter';
import { getSerializedKindDependency, serializedDependency } from '../../common/data/serialized-dependency';
import { concatIf } from '../../../../utils/array';
import { when } from '../../../../utils/string';
import { getJSDoc, getKindValue, getSafePropertyName, getURL, HTTPMethod } from '../../common/utils';
import { getJSDoc, getKindValue, getSafePropertyName, getURL, HTTPMethod, XHRResponseType } from '../../common/utils';
import { Either, isLeft, left, right } from 'fp-ts/lib/Either';
import { array, either, nonEmptyArray, option, record } from 'fp-ts';
import { combineEither } from '@devexperts/utils/dist/adt/either.utils';
Expand Down Expand Up @@ -271,6 +271,22 @@ export const serializeOperationObject = combineReader(
map(() => `@deprecated`),
);

const responseType: XHRResponseType = pipe(
operation.produces,
fold(
() => 'json',
produces => {
if (produces.includes('application/octet-stream')) {
return 'blob';
}
if (produces.includes('text/plain')) {
return 'text';
}
return 'json';
},
),
);

return combineEither(
parameters,
serializedResponses,
Expand Down Expand Up @@ -342,6 +358,7 @@ export const serializeOperationObject = combineReader(
e.httpClient.request({
url: ${getURL(url, parameters.serializedPathParameters)},
method: '${method}',
responseType: '${responseType}',
${when(hasQueryParameters, 'query,')}
${when(hasBodyParameters, 'body,')}
${when(hasHeaderParameters, 'headers')}
Expand All @@ -351,7 +368,7 @@ export const serializeOperationObject = combineReader(
${serializedResponses.io}.decode(value),
either.mapLeft(ResponseValidationError.create),
either.fold(error => e.httpClient.throwError(error), decoded => e.httpClient.of(decoded)),
),
),
);
},
`;
Expand Down
42 changes: 39 additions & 3 deletions src/language/typescript/3.0/serializers/operation-object.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { getJSDoc, getKindValue, getSafePropertyName, getTypeName, getURL, HTTPMethod } from '../../common/utils';
import {
getJSDoc,
getKindValue,
getSafePropertyName,
getTypeName,
getURL,
HTTPMethod,
SUCCESSFUL_CODES,
XHRResponseType,
} from '../../common/utils';
import {
getSerializedPropertyType,
getSerializedObjectType,
Expand Down Expand Up @@ -34,7 +43,7 @@ import { ResolveRefContext, fromString, getRelativePath, Ref } from '../../../..
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 { isSome, none, Option, some } from 'fp-ts/lib/Option';
import { chain, isSome, none, Option, some, map, fromEither, 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 @@ -49,6 +58,8 @@ import {
SerializedFragment,
} from '../../common/data/serialized-fragment';
import { SchemaObjectCodec } from '../../../../schema/3.0/schema-object';
import { lookup, keys } from 'fp-ts/lib/Record';
import { ResponseObjectCodec } from '../../../../schema/3.0/response-object';
import {
fromSerializedHeaderParameter,
getSerializedHeaderParameterType,
Expand Down Expand Up @@ -256,8 +267,9 @@ export const getParameters = combineReader(
);

export const serializeOperationObject = combineReader(
ask<ResolveRefContext>(),
getParameters,
getParameters => (
(e, getParameters) => (
pattern: string,
method: HTTPMethod,
from: Ref,
Expand All @@ -274,6 +286,29 @@ export const serializeOperationObject = combineReader(
);

const serializedResponses = serializeResponsesObject(from)(operation.responses);
const responseType: XHRResponseType = pipe(
SUCCESSFUL_CODES,
array.findFirstMap(code => lookup(code, operation.responses)),
chain(response =>
ReferenceObjectCodec.is(response)
? fromEither(e.resolveRef(response.$ref, ResponseObjectCodec))
: some(response),
),
chain(response => response.content),
map(keys),
fold(
() => 'json',
types => {
if (types.includes('application/octet-stream')) {
return 'blob';
}
if (types.includes('text/plain')) {
return 'text';
}
return 'json';
},
),
);

return combineEither(
parameters,
Expand Down Expand Up @@ -346,6 +381,7 @@ export const serializeOperationObject = combineReader(
e.httpClient.request({
url: ${getURL(pattern, parameters.serializedPathParameters)},
method: '${method}',
responseType: '${responseType}',
${when(hasQueryParameters, 'query,')}
${when(hasBodyParameter, 'body,')}
${when(hasHeaderParameters, 'headers')}
Expand Down
4 changes: 3 additions & 1 deletion src/language/typescript/3.0/serializers/response-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export const serializeResponseObject = (
): Option<Either<Error, SerializedType>> =>
pipe(
responseObject.content,
option.mapNullable(content => content['application/json']),
option.mapNullable(
content => content['application/json'] || content['text/plain'] || content['application/octet-stream'],
),
option.chain(media => media.schema),
option.map(schema =>
ReferenceObjectCodec.is(schema)
Expand Down
1 change: 1 addition & 0 deletions src/language/typescript/common/bundled/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const client = `
export interface Request {
readonly method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
readonly url: string;
readonly responseType: 'json' | 'blob' | 'text';
readonly query?: string;
readonly body?: unknown;
readonly headers?: Record<string, unknown>;
Expand Down
9 changes: 9 additions & 0 deletions src/language/typescript/common/data/serialized-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export const SERIALIZED_DATETIME_TYPE = serializedType(
[serializedDependency('DateFromISOString', 'io-ts-types/lib/DateFromISOString')],
[],
);
export const SERIALIZED_DATE_TYPE = serializedType(
'Date',
'DateFromISODateStringIO',
[serializedDependency('DateFromISODateStringIO', '../utils/utils')],
[],
);
export const SERIALIZED_STRING_TYPE = serializedType('string', 'string', [serializedDependency('string', 'io-ts')], []);
export const getSerializedStringType = (from: Ref, format: Option<string>): Either<Error, SerializedType> => {
return combineEither(utilsRef, utilsRef => {
Expand All @@ -103,6 +109,9 @@ export const getSerializedStringType = (from: Ref, format: Option<string>): Eith
),
);
}
case 'binary': {
return some(SERIALIZED_UNKNOWN_TYPE);
}
}
return none;
}),
Expand Down
2 changes: 1 addition & 1 deletion src/language/typescript/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const SUCCESSFUL_CODES = ['200', '201', 'default'];
export const CONTROLLERS_DIRECTORY = 'controllers';
export const DEFINITIONS_DIRECTORY = 'definitions';
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';

export type XHRResponseType = 'json' | 'blob' | 'text';
const INVALID_NAMES = ['Error', 'Promise', 'PromiseLike', 'Array', 'ArrayLike', 'Function', 'Object'];
const TYPE_NAME_NORMALIZE_REGEX = /\W/g;
const normalize = (name: string): string => name.replace(TYPE_NAME_NORMALIZE_REGEX, '_').replace(/^(\d)/, '_$1');
Expand Down
12 changes: 12 additions & 0 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ describe('codegen', () => {
),
);

it(
'specs/3.0/file-and-text.yml',
unsafe(
generate({
spec: path.resolve(__dirname, 'specs/3.0/file-and-text.yml'),
out,
language: serializeOpenAPI3,
decoder: OpenapiObjectCodec,
}),
),
);

it(
'specs/asyncapi-2.0.0/streetlights-api.yml',
unsafe(
Expand Down
106 changes: 106 additions & 0 deletions test/specs/2.0/yaml/demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,107 @@ paths:
type: number
/shared-path-item:
$ref: '../../arbitrary.yml#/SharedPathItem'
/file/{filedId}/version/{version}:
parameters:
- in: path
type: number
required: true
name: filedId
- in: path
type: number
required: true
name: version
get:
tags:
- files
summary: GetSomeFile
operationId: getFile
produces:
- application/octet-stream
responses:
200:
description: succesfull operation
schema:
type: string
format: binary
/fileWithResponseRef:
get:
tags:
- files
summary: GetFileWithResponseRef
operationId: getFileWithResponseRef
produces:
- application/octet-stream
responses:
200:
$ref: '#/responses/SuccessfulFile'
/fileWithSchemaRef:
get:
tags:
- files
summary: GetFileWithSchemaRef
operationId: getFileWithSchemaRef
produces:
- application/octet-stream
responses:
200:
description: succesfull operation
schema:
$ref: '#/definitions/File'
/text/{textId}/version/{version}:
parameters:
- in: path
type: number
required: true
name: textId
- in: path
type: number
required: true
name: version
get:
tags:
- text
summary: GetSomeText
operationId: getText
produces:
- text/plain
responses:
200:
description: succesfull operation
schema:
type: string
/textWithResponseRef:
get:
tags:
- text
summary: GetTextWithResponseRef
operationId: getTextWithResponseRef
produces:
- text/plain
responses:
200:
$ref: '#/responses/SuccessfulText'
/textWithSchemaRef:
get:
tags:
- text
summary: GetTextWithSchemaRef
operationId: getTextWithSchemaRef
produces:
- text/plain
responses:
200:
description: succesfull operation
schema:
$ref: '#/definitions/Text'
definitions:
Id:
type: integer
File:
type: string
format: binary
Text:
type: string
local:
type: object
required:
Expand Down Expand Up @@ -150,3 +248,11 @@ responses:
description: General Error
schema:
$ref: '#/definitions/GeneralError'
SuccessfulFile:
description: succesful file data loading
schema:
$ref: '#/definitions/File'
SuccessfulText:
description: succesful text data loading
schema:
$ref: '#/definitions/Text'
Loading

0 comments on commit 977f534

Please sign in to comment.