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

[JUG-14] - Use @stoplight/elements to show the OpenAPI specification #34

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/rare-avocados-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@pagopa-dev/juggler': minor
---

Replace swagger-ui-react with @stoplight/elements
8,124 changes: 4,713 additions & 3,411 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"openapi": {
"pn": {
"deliveryURL": "https://github.com/pagopa/pn-delivery/raw/d499410/docs/openapi/api-external-b2b-pa-v1.yaml",
"deliveryURL": "https://raw.githubusercontent.com/pagopa/pn-delivery/5534854/docs/openapi/api-external-b2b-pa.yaml",
"streamURL": "https://github.com/pagopa/pn-delivery-push/raw/a886f32/docs/openapi/api-external-b2b-webhook-v1.yaml"
},
"io": {
Expand All @@ -35,7 +35,7 @@
"dev": "npm run generate:api && npm run compile && node -r dotenv/config dist/main.js dotenv_config_path=.env.default",
"build": "npm run compile && next build",
"lint": "next lint",
"format": "prettier --write \"./**/*.ts\"",
"format": "prettier --write \"./**/*.{ts,tsx}\"",
"dev:pn": "OPENAPI_URL=$npm_package_config_openapi_pn_deliveryURL npm run dev",
"dev:io": "OPENAPI_URL=$npm_package_config_openapi_io_apiPublicURL npm run dev",
"dev:iosign": "OPENAPI_URL=$npm_package_config_openapi_iosign_issuerURL npm run dev",
Expand All @@ -54,7 +54,6 @@
"@types/jest": "^29.4.0",
"@types/node": "^18.15.11",
"@types/react": "18.0.28",
"@types/swagger-ui-react": "^4.11.0",
"concurrently": "^7.6.0",
"dependency-check": "^4.1.0",
"dotenv": "^16.0.3",
Expand All @@ -74,6 +73,7 @@
"@emotion/styled": "^11.10.6",
"@mui/material": "^5.11.11",
"@pagopa/ts-commons": "^10.14.2",
"@stoplight/elements": "^7.7.18",
"@stoplight/prism-cli": "^4.10.5",
"@stoplight/prism-http": "^4.10.5",
"@textea/json-viewer": "^1.24.5",
Expand All @@ -85,7 +85,6 @@
"newtype-ts": "^0.3.5",
"next": "^13.1.6",
"pino": "^8.8.0",
"swagger-ui-react": "^4.18.0",
"swr": "^2.0.4"
}
}
5 changes: 4 additions & 1 deletion src/adapters/express/AppEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Config } from '../../config';
import { Capabilities } from '../../domain/Capabilities';
import { makeRequestResponseStore } from '../array/makeRequestResponseStore';
import { makeMock } from '../prism/makeMock';
import { makeOpenAPIParser } from '../swagger-parser/makeOpenAPIParser';

export type AppEnv = Capabilities & Config;

Expand All @@ -15,10 +16,12 @@ export const makeAppEnv = (config: Config): TE.TaskEither<Error, AppEnv> =>
TE.Do,
TE.apS('mock', makeMock(config.openapi.URL)),
TE.apS('requestResponseStore', TE.of(makeRequestResponseStore([]))),
TE.map(({ mock, requestResponseStore }) => ({
TE.apS('openAPIParser', TE.of(makeOpenAPIParser())),
TE.map(({ mock, requestResponseStore, openAPIParser }) => ({
...config,
mock,
requestResponseReader: requestResponseStore,
requestResponseWriter: requestResponseStore,
openApiParser: openAPIParser,
}))
);
17 changes: 8 additions & 9 deletions src/adapters/express/getOpenApiRouter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import express from 'express';
import { pipe } from 'fp-ts/lib/function';
import * as E from 'fp-ts/Either';
import * as TE from 'fp-ts/TaskEither';
import SwaggerParser from '@apidevtools/swagger-parser';
import * as RTE from 'fp-ts/ReaderTaskEither';
import { getOpenApiSpec } from '../../useCases/getOpenApiSpec';
import { AppEnv } from './AppEnv';
import { problemDetail500 } from './errors';

Expand All @@ -12,13 +11,13 @@ export const makeGetOpenApiRouter = (env: AppEnv): express.Router => {
// In the future this feature can be mapped into the domain
router.get('/api/openapi', (_req, res) =>
pipe(
TE.tryCatch(() => SwaggerParser.validate(env.openapi.URL), E.toError),
TE.bimap(
() => res.status(500).json(problemDetail500),
(openapi) => res.status(200).json(openapi)
getOpenApiSpec(env.server)(env.openapi.URL),
RTE.fold(
(_) => RTE.of(res.status(500).json(problemDetail500)),
(spec) => RTE.of(res.status(200).json(spec))
),
TE.toUnion
)()
RTE.toUnion
)(env)()
);

return router;
Expand Down
18 changes: 18 additions & 0 deletions src/adapters/swagger-parser/makeOpenAPIParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as TE from 'fp-ts/TaskEither';
import { pipe } from 'fp-ts/function';
import SwaggerParser from '@apidevtools/swagger-parser';
import * as E from 'fp-ts/Either';
import { OpenAPIParser } from '../../domain/OpenAPISpec';

export const makeOpenAPIParser = (): OpenAPIParser => ({
parse: (url) =>
pipe(
TE.tryCatch(() => SwaggerParser.validate(url), E.toError),
TE.map(
(openapi) =>
// TODO: we should change the baseURL coming from the OpenAPI spec. We want them to have the Juggler's baseUrl.
// FIXME: Continue here
openapi
)
),
});
2 changes: 2 additions & 0 deletions src/domain/Capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
RequestResponseReader,
RequestResponseWriter,
} from './RequestResponse';
import { OpenAPIParser } from './OpenAPISpec';

/**
* Maps the capabilities to a given property name. Pick the capability using the
Expand All @@ -18,4 +19,5 @@ export type Capabilities = {
mock: Mock;
requestResponseReader: RequestResponseReader;
requestResponseWriter: RequestResponseWriter;
openApiParser: OpenAPIParser;
};
15 changes: 15 additions & 0 deletions src/domain/OpenAPISpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as TE from 'fp-ts/TaskEither';
import { OpenAPI } from 'openapi-types';

// For now use the types of OpenAPI types
export type OpenAPISpec = OpenAPI.Document;

/**
* This type express the capability to parse an OpenAPI specification from an URL.
*/
export type OpenAPIParser = {
/**
* Given an URL, parse the OpenAPI specification.
*/
parse: (url: string) => TE.TaskEither<Error, OpenAPISpec>;
};
1 change: 1 addition & 0 deletions src/domain/__tests__/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const makeFakeCapabilities = (defaultData: typeof data = data) => {
mock: mock<Capabilities['mock']>(),
requestResponseReader: mock<Capabilities['requestResponseReader']>(),
requestResponseWriter: mock<Capabilities['requestResponseWriter']>(),
openApiParser: mock<Capabilities['openApiParser']>(),
};
// default behavior
mocked.mock.generateResponse.mockReturnValue(
Expand Down
15 changes: 12 additions & 3 deletions src/pages/openapi.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
import { API } from '@stoplight/elements';
import '@stoplight/elements/styles.min.css';

const ApiViewer = () => <SwaggerUI url="/api/openapi" />;
const ApiViewer = () => (
<API
apiDescriptionUrl="/api/openapi"
basePath={'/ui/openapi'}
router={typeof window === 'undefined' ? 'memory' : 'history'}
hideInternal={true}
hideExport={true}
hideTryIt={true} // We are going to enable in later
/>
);

export default ApiViewer;
11 changes: 11 additions & 0 deletions src/useCases/getOpenApiSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { pipe } from 'fp-ts/function';
import * as R from 'fp-ts/Reader';
import { Config } from '../config';
import { Capabilities } from '../domain/Capabilities';

export const getOpenApiSpec =
(_serverConfig: Config['server']) => (openApiURL: string) =>
pipe(
R.ask<Pick<Capabilities, 'openApiParser'>>(),
R.map(({ openApiParser }) => openApiParser.parse(openApiURL))
);