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

fix: fastify schema, release 0.1.57 #295

Merged
merged 6 commits into from
Mar 19, 2023
Merged
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ Unreleased changes are in the `master` branch.

## [Unreleased]

## [0.1.57] - 2023-03-17

### Fixed

- `getSchemaForEndpoint` compatibility with fast-json-stringify (array `type` not supported except for `["<type>", "null"]`)
- `getSchemaForEndpoint` compatibility with fast-json-stringify (array in `type` not supported except for `["<type>", "null"]`, fix for nested arbitrary objects)
- generated ts types and schema for `/ipfs/gateway/{IPFS_path}`

## [0.1.56] - 2023-03-15

Expand Down
7 changes: 6 additions & 1 deletion openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
openapi: 3.1.0
info:
version: 0.1.56
version: 0.1.57
title: Blockfrost.io ~ API Documentation
x-logo:
url: https://staging.blockfrost.io/images/logo.svg
Expand Down Expand Up @@ -4550,6 +4550,11 @@ paths:
responses:
'200':
description: Returns the object content
content:
application/octet-stream:
schema:
type: string
format: binary
'400':
$ref: '#/components/responses/400'
'403':
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@blockfrost/openapi",
"version": "0.1.56",
"version": "0.1.57",
"description": "OpenAPI specifications for blockfrost.io",
"repository": "git@github.com:blockfrost/openapi.git",
"author": "admin@blockfrost.io",
Expand All @@ -24,11 +24,11 @@
"@vitest/coverage-c8": "^0.25.3",
"ajv": "^8.11.2",
"core-js": "3.1.4",
"openapi-typescript": "^6.1.0",
"openapi-typescript": "6.1.0",
"react-is": "16.8.2",
"redoc-cli": "^0.13.20",
"rimraf": "^3.0.2",
"typescript": "^4.8.4",
"typescript": "^5.0.2",
"vite": "^3.2.4",
"vitest": "^0.24.3"
},
Expand Down
2 changes: 1 addition & 1 deletion src/definitions.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
openapi: 3.1.0
info:
version: "0.1.56"
version: "0.1.57"
title: Blockfrost.io ~ API Documentation
x-logo:
url: https://staging.blockfrost.io/images/logo.svg
Expand Down
78 changes: 66 additions & 12 deletions src/functions/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,51 @@ const file = fs.readFileSync(
);
const spec = YAML.parse(file);

export const convertType = (schema: any) => {
export const transformSchemaElement = (schema: any): any => {
// To generate response schema supported by fast-json-stringify
// We need to convert array type (["null", "<other type>"]) to type: "<other type>" with nullable set to true.
// Note: Alternative approach for values with multiple types is to use anyOf/oneOf.
// https://github.com/fastify/fast-json-stringify#anyof-and-oneof

if (schema.type === 'object' && schema.properties) {
// convert type in object properties
for (const property of Object.keys(schema.properties)) {
schema.properties[property] = convertType(schema.properties[property]);
for (const propertyKey of Object.keys(schema.properties)) {
const property = schema.properties[propertyKey];
if (
property.type === 'object' &&
property.additionalProperties === true &&
!property.properties
) {
if (!property.anyOf && !property.oneOf) {
// Workaround for fast-json-stringify
// If object's property is arbitrary object,
// convert {type: 'object', additionalProperties: true} to {}
delete schema.properties[propertyKey].type;
delete schema.properties[propertyKey].additionalProperties;
}
}
if (property.anyOf) {
if (
property.anyOf.find(
(p: unknown) =>
typeof p === 'object' &&
p !== null &&
'type' in p &&
p.type === 'null',
)
) {
// if array of anyOf items includes {"type": "null"} then set nullable to true on the parent
property.nullable = true;
}
}
schema.properties[propertyKey] = transformSchemaElement(
schema.properties[propertyKey],
);
}
return schema;
} else if (schema.type === 'array' && schema.items) {
// convert type in array items
schema.items = convertType(schema.items);
schema.items = transformSchemaElement(schema.items);
return schema;
} else if (Array.isArray(schema.type)) {
const isNullable = schema.type.includes('null');
Expand All @@ -41,11 +71,12 @@ export const convertType = (schema: any) => {
)}. Type doesn't support an array with multiple values. Use anyOf/oneOf.`,
);
}
return {

return transformSchemaElement({
...schema,
type: schema.type.filter((a: string) => a !== 'null')[0],
nullable: true,
};
});
} else {
// edge case where type is an array with only 1 element
if (schema.type.length === 1) {
Expand Down Expand Up @@ -85,8 +116,12 @@ export const getSchemaForEndpoint = (endpointName: string) => {
for (const response of Object.keys(endpoint.responses)) {
// success 200
if (response === '200') {
const contentType =
'application/octet-stream' in endpoint.responses['200'].content
? 'application/octet-stream'
: 'application/json';
const referenceOrValue =
endpoint.responses['200'].content['application/json'].schema;
endpoint.responses['200'].content[contentType].schema;

// is reference -> resolve references
if (referenceOrValue['$ref']) {
Expand All @@ -107,24 +142,24 @@ export const getSchemaForEndpoint = (endpointName: string) => {
);

if (schemaReferenceOrValue.type) {
responses.response[200] = convertType({
responses.response[200] = transformSchemaElement({
...schemaReferenceOrValue,
items: spec.components.schemas[nestedSchemaName],
});
} else {
responses.response[200] = convertType(
responses.response[200] = transformSchemaElement(
spec.components.schemas[nestedSchemaName],
);
}
} else {
// is not nested reference
responses.response[200] = convertType(
responses.response[200] = transformSchemaElement(
spec.components.schemas[schemaName],
);
}
} else {
// is not reference
responses.response[200] = convertType(referenceOrValue);
responses.response[200] = transformSchemaElement(referenceOrValue);
}

// anyOf case
Expand All @@ -137,7 +172,9 @@ export const getSchemaForEndpoint = (endpointName: string) => {
'',
);

const item = convertType(spec.components.schemas[schemaName]);
const item = transformSchemaElement(
spec.components.schemas[schemaName],
);
anyOfResult['anyOf'].push(item);
}

Expand Down Expand Up @@ -196,6 +233,7 @@ export const getSchemaForEndpoint = (endpointName: string) => {
}

if (endpointName === '/scripts/{script_hash}/json') {
// TODO: no longer necessary
responses.response[200] = scriptsJsonSchema;
}
}
Expand All @@ -215,6 +253,22 @@ export const getSchemaForEndpoint = (endpointName: string) => {
return responses;
};

export const generateSchemas = () => {
// Returns fast-json-stringify compatible schema object indexed by endpoint name
const endpoints = Object.keys(spec.paths);

const schemas: Record<string, unknown> = {};
for (const endpoint of endpoints) {
try {
schemas[endpoint] = getSchemaForEndpoint(endpoint);
} catch (error) {
console.error(`Error while processing endpoint ${endpoint}`);
throw error;
}
}
return schemas;
};

export const getSchema = (schemaName: string) => {
if (!spec.components.schemas[schemaName]) {
throw Error(`Missing Blockfrost OpenAPI schema with name "${schemaName}".`);
Expand Down
6 changes: 5 additions & 1 deletion src/generated-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3491,7 +3491,11 @@ export interface paths {
};
responses: {
/** @description Returns the object content */
200: never;
200: {
content: {
"application/octet-stream": string;
};
};
400: components["responses"]["400"];
403: components["responses"]["403"];
404: components["responses"]["404"];
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
getSchemaForEndpoint,
getSchema,
validateSchema,
generateSchemas,
} from './functions/schema';
import {
getOnchainMetadata,
Expand All @@ -13,6 +14,7 @@ import {
export {
getSchemaForEndpoint,
getSchema,
generateSchemas,
validateSchema,
getOnchainMetadata,
getCIPstandard,
Expand Down
8 changes: 8 additions & 0 deletions src/paths/ipfs/gateway/{IPFS_path}/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ get:
description: Path to the IPFS object
responses:
"200":
# TODO: If no type is specified then generated TS type is never instead of unknown
# https://github.com/drwpow/openapi-typescript/issues/1039
# As a workaround return binary data
description: Returns the object content
content:
application/octet-stream:
schema:
type: string
format: binary
"400":
$ref: ../../../../responses/errors/400.yaml
"403":
Expand Down
22 changes: 20 additions & 2 deletions test/fixtures/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const convertType = [
export const transformSchemaElement = [
{
description: 'perfectly fine object that does not need transformation',
data: {
Expand Down Expand Up @@ -107,9 +107,27 @@ export const convertType = [
type: 'object',
},
},
{
description: 'object with nested arbitrary object',
data: {
type: 'object',
properties: {
key: {
type: 'object',
additionalProperties: true,
},
},
},
result: {
type: 'object',
properties: {
key: {},
},
},
},
];

export const convertTypeError = [
export const transformSchemaElementError = [
{
description: 'array type with 2 types should throw',
data: {
Expand Down
Loading