Skip to content

Commit

Permalink
Merge pull request #295 from blockfrost/chore/release0157
Browse files Browse the repository at this point in the history
fix: fastify schema, release 0.1.57
  • Loading branch information
vladimirvolek authored Mar 19, 2023
2 parents f1d31b0 + 8ad258e commit 1fb72dd
Show file tree
Hide file tree
Showing 13 changed files with 22,132 additions and 42 deletions.
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 @@ -4585,6 +4585,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 @@ -3529,7 +3529,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 @@ -17,7 +17,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

0 comments on commit 1fb72dd

Please sign in to comment.