Fix API definitions and update types.

Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
Aramis Sennyey
2023-02-22 17:00:48 -05:00
committed by Fredrik Adelöw
parent 8eda7722a3
commit 4669b87364
10 changed files with 1367 additions and 74 deletions
+33 -13
View File
@@ -6,6 +6,7 @@ info:
description: The Backstage backend plugin that provides the Backstage catalog
license:
name: Apache-2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
contact: {}
servers:
@@ -18,17 +19,6 @@ components:
requestBodies: {}
responses: {}
schemas:
RefreshOptions:
type: object
properties:
authorizationToken:
type: string
entityRef:
type: string
description: The reference to a single entity that should be refreshed
required:
- entityRef
description: Options for requesting a refresh of entities in the catalog.
JsonObject:
type: object
properties: {}
@@ -57,6 +47,7 @@ components:
required:
- url
description: A link to external information that is related to the entity.
additionalProperties: false
EntityMeta:
allOf:
- $ref: '#/components/schemas/JsonObject'
@@ -133,6 +124,7 @@ components:
required:
- name
description: Metadata fields common to all versions/kinds of entity.
additionalProperties: false
EntityRelation:
type: object
properties:
@@ -146,6 +138,7 @@ components:
- targetRef
- type
description: A relation of a specific type to another entity in the catalog.
additionalProperties: false
Entity:
type: object
properties:
@@ -171,6 +164,7 @@ components:
- kind
- apiVersion
description: The parts of the format that's common to all versions/kinds of entity.
additionalProperties: false
EntityAncestryResponse:
type: object
properties:
@@ -193,6 +187,7 @@ components:
required:
- items
- rootEntityRef
additionalProperties: false
EntitiesBatchResponse:
type: object
properties:
@@ -207,6 +202,7 @@ components:
that are null signify that no entity existed with that ref.
required:
- items
additionalProperties: false
EntityFacet:
type: object
properties:
@@ -214,6 +210,7 @@ components:
type: string
count:
type: number
additionalProperties: false
EntityFacets:
type: array
items:
@@ -227,6 +224,7 @@ components:
$ref: '#/components/schemas/EntityFacets'
required:
- facets
additionalProperties: false
Location:
type: object
properties:
@@ -241,6 +239,7 @@ components:
- type
- id
description: Entity location for a specific entity.
additionalProperties: false
LocationSpec:
type: object
properties:
@@ -257,6 +256,7 @@ components:
- target
- type
description: Holds the entity location information.
additionalProperties: false
AnalyzeLocationExistingEntity:
type: object
properties:
@@ -275,6 +275,7 @@ components:
read and emitted like this so that the frontend can inform the user that it
located them and can make sure to register them as well if they weren't
already
additionalProperties: false
RecursivePartialEntityRelation:
type: object
properties:
@@ -285,6 +286,7 @@ components:
type: string
description: The type of the relation.
description: A relation of a specific type to another entity in the catalog.
additionalProperties: false
RecursivePartialEntityMeta:
allOf:
- $ref: '#/components/schemas/JsonObject'
@@ -360,6 +362,7 @@ components:
update or delete operations, but the server is free to reject requests
that do so in such a way that it breaks semantics.
description: Metadata fields common to all versions/kinds of entity.
additionalProperties: false
RecursivePartial_Entity_:
type: object
properties:
@@ -381,6 +384,7 @@ components:
$ref: '#/components/schemas/RecursivePartialEntityRelation'
description: The relations that this entity has with other entities.
description: Makes all keys of an entire hierarchy optional.
additionalProperties: false
AnalyzeLocationEntityField:
type: object
properties:
@@ -411,6 +415,7 @@ components:
- value
- state
- field
additionalProperties: false
AnalyzeLocationGenerateEntity:
type: object
properties:
@@ -429,6 +434,7 @@ components:
the frontend. It'll probably contain a (possibly incomplete) entity, plus
enough info for the frontend to know what form data to show to the user
for overriding/completing the info.
additionalProperties: false
AnalyzeLocationResponse:
type: object
properties:
@@ -443,6 +449,7 @@ components:
required:
- generateEntities
- existingEntityFiles
additionalProperties: false
LocationInput:
type: object
properties:
@@ -459,6 +466,7 @@ components:
- type
- target
- presence
additionalProperties: false
SerializedError:
allOf:
- $ref: '#/components/schemas/JsonObject'
@@ -480,13 +488,14 @@ components:
- message
- name
description: The serialized form of an Error.
additionalProperties: false
EntitiesQueryResponse:
type: object
properties:
items:
type: array
items:
- $ref: '#/components/schemas/Entity'
$ref: '#/components/schemas/Entity'
description: |-
The list of entities paginated by a specific filter.
totalItems:
@@ -502,6 +511,7 @@ components:
type: string
description: |-
Base64 encoded database query for the previous page.
additionalProperties: false
securitySchemes:
JWT:
type: http
@@ -523,7 +533,17 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/RefreshOptions'
type: object
properties:
authorizationToken:
type: string
entityRef:
type: string
description: The reference to a single entity that should be refreshed
required:
- entityRef
description: Options for requesting a refresh of entities in the catalog.
additionalProperties: false
/entities:
get:
operationId: GetEntities
@@ -22,6 +22,7 @@ export default {
'The Backstage backend plugin that provides the Backstage catalog',
license: {
name: 'Apache-2.0',
url: 'http://www.apache.org/licenses/LICENSE-2.0.html',
},
contact: {},
},
@@ -37,22 +38,6 @@ export default {
requestBodies: {},
responses: {},
schemas: {
RefreshOptions: {
type: 'object',
properties: {
authorizationToken: {
type: 'string',
},
entityRef: {
type: 'string',
description:
'The reference to a single entity that should be refreshed',
},
},
required: ['entityRef'],
description:
'Options for requesting a refresh of entities in the catalog.',
},
JsonObject: {
type: 'object',
properties: {},
@@ -91,6 +76,7 @@ export default {
required: ['url'],
description:
'A link to external information that is related to the entity.',
additionalProperties: false,
},
EntityMeta: {
allOf: [
@@ -156,6 +142,7 @@ export default {
},
],
description: 'Metadata fields common to all versions/kinds of entity.',
additionalProperties: false,
},
EntityRelation: {
type: 'object',
@@ -172,6 +159,7 @@ export default {
required: ['targetRef', 'type'],
description:
'A relation of a specific type to another entity in the catalog.',
additionalProperties: false,
},
Entity: {
type: 'object',
@@ -203,6 +191,7 @@ export default {
required: ['metadata', 'kind', 'apiVersion'],
description:
"The parts of the format that's common to all versions/kinds of entity.",
additionalProperties: false,
},
EntityAncestryResponse: {
type: 'object',
@@ -230,6 +219,7 @@ export default {
},
},
required: ['items', 'rootEntityRef'],
additionalProperties: false,
},
EntitiesBatchResponse: {
type: 'object',
@@ -249,6 +239,7 @@ export default {
},
},
required: ['items'],
additionalProperties: false,
},
EntityFacet: {
type: 'object',
@@ -260,6 +251,7 @@ export default {
type: 'number',
},
},
additionalProperties: false,
},
EntityFacets: {
type: 'array',
@@ -278,6 +270,7 @@ export default {
},
},
required: ['facets'],
additionalProperties: false,
},
Location: {
type: 'object',
@@ -294,6 +287,7 @@ export default {
},
required: ['target', 'type', 'id'],
description: 'Entity location for a specific entity.',
additionalProperties: false,
},
LocationSpec: {
type: 'object',
@@ -311,6 +305,7 @@ export default {
},
required: ['target', 'type'],
description: 'Holds the entity location information.',
additionalProperties: false,
},
AnalyzeLocationExistingEntity: {
type: 'object',
@@ -328,6 +323,7 @@ export default {
required: ['entity', 'isRegistered', 'location'],
description:
"If the folder pointed to already contained catalog info yaml files, they are\nread and emitted like this so that the frontend can inform the user that it\nlocated them and can make sure to register them as well if they weren't\nalready",
additionalProperties: false,
},
RecursivePartialEntityRelation: {
type: 'object',
@@ -343,6 +339,7 @@ export default {
},
description:
'A relation of a specific type to another entity in the catalog.',
additionalProperties: false,
},
RecursivePartialEntityMeta: {
allOf: [
@@ -409,6 +406,7 @@ export default {
'Metadata fields common to all versions/kinds of entity.',
},
],
additionalProperties: false,
},
RecursivePartial_Entity_: {
type: 'object',
@@ -438,6 +436,7 @@ export default {
},
},
description: 'Makes all keys of an entire hierarchy optional.',
additionalProperties: false,
},
AnalyzeLocationEntityField: {
type: 'object',
@@ -468,6 +467,7 @@ export default {
},
},
required: ['description', 'value', 'state', 'field'],
additionalProperties: false,
},
AnalyzeLocationGenerateEntity: {
type: 'object',
@@ -485,6 +485,7 @@ export default {
required: ['fields', 'entity'],
description:
"This is some form of representation of what the analyzer could deduce.\nWe should probably have a chat about how this can best be conveyed to\nthe frontend. It'll probably contain a (possibly incomplete) entity, plus\nenough info for the frontend to know what form data to show to the user\nfor overriding/completing the info.",
additionalProperties: false,
},
AnalyzeLocationResponse: {
type: 'object',
@@ -503,6 +504,7 @@ export default {
},
},
required: ['generateEntities', 'existingEntityFiles'],
additionalProperties: false,
},
LocationInput: {
type: 'object',
@@ -519,6 +521,7 @@ export default {
},
},
required: ['type', 'target', 'presence'],
additionalProperties: false,
},
SerializedError: {
allOf: [
@@ -550,17 +553,16 @@ export default {
},
],
description: 'The serialized form of an Error.',
additionalProperties: false,
},
EntitiesQueryResponse: {
type: 'object',
properties: {
items: {
type: 'array',
items: [
{
$ref: '#/components/schemas/Entity',
},
],
items: {
$ref: '#/components/schemas/Entity',
},
description: 'The list of entities paginated by a specific filter.',
},
totalItems: {
@@ -581,6 +583,7 @@ export default {
},
},
},
additionalProperties: false,
},
},
securitySchemes: {
@@ -612,7 +615,21 @@ export default {
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/RefreshOptions',
type: 'object',
properties: {
authorizationToken: {
type: 'string',
},
entityRef: {
type: 'string',
description:
'The reference to a single entity that should be refreshed',
},
},
required: ['entityRef'],
description:
'Options for requesting a refresh of entities in the catalog.',
additionalProperties: false,
},
},
},
+11 -3
View File
@@ -25,14 +25,22 @@ export async function createRouter(
console.log(options);
const router = Router() as ApiRouter<DeepWriteable<typeof doc>>;
router.get('/pets/:uid', (req, res) => {
router.get('/pets/:uid', (_, res) => {
res.json({
id: 1,
name: req.params.uid,
name: 'test',
});
});
// router.get('/pet') will complain with a TS error
router.get('/pets', (_, res) => {
res.json([
{
id: 1,
tag: '123',
name: 'test',
},
]);
});
router.post('/pets', (_, res) => {
res.send();
+7 -7
View File
@@ -23,17 +23,17 @@ export type DeepWriteable<T> = {
export interface ApiRouter<Doc extends RequiredDoc> extends Router {
get: DocRequestMatcher<Doc, this, 'get'>;
post: DocRequestMatcher<Doc, this, 'get'>;
post: DocRequestMatcher<Doc, this, 'post'>;
all: DocRequestMatcher<Doc, this, 'get'>;
all: DocRequestMatcher<Doc, this, 'all'>;
put: DocRequestMatcher<Doc, this, 'get'>;
put: DocRequestMatcher<Doc, this, 'put'>;
delete: DocRequestMatcher<Doc, this, 'get'>;
delete: DocRequestMatcher<Doc, this, 'delete'>;
patch: DocRequestMatcher<Doc, this, 'get'>;
patch: DocRequestMatcher<Doc, this, 'patch'>;
options: DocRequestMatcher<Doc, this, 'get'>;
options: DocRequestMatcher<Doc, this, 'options'>;
head: DocRequestMatcher<Doc, this, 'get'>;
head: DocRequestMatcher<Doc, this, 'head'>;
}
File diff suppressed because it is too large Load Diff
@@ -156,6 +156,7 @@ export default {
type: 'string',
},
},
additionalProperties: false,
},
Pets: {
type: 'array',
@@ -175,6 +176,7 @@ export default {
type: 'string',
},
},
additionalProperties: false,
},
},
},
@@ -131,7 +131,7 @@ export type ObjectWithContentSchema<
Object extends { content?: ContentObject },
> = Object['content'] extends ContentObject
? SchemaRef<Doc, Object['content']['application/json']['schema']>
: unknown;
: never;
/**
* From https://stackoverflow.com/questions/71393738/typescript-intersection-not-union-type-from-json-schema.
@@ -164,3 +164,7 @@ export type ConvertAll<T, R extends ReadonlyArray<unknown> = []> = T extends [
]
? ConvertAll<Rest, [...R, FromSchema<First>]>
: R;
type UnknownIfNever<P> = [P] extends [never] ? unknown : P;
export type ToTypeSafe<T> = UnknownIfNever<ConvertAll<TuplifyUnion<T>>[number]>;
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Application } from 'express';
import core from 'express-serve-static-core';
import { DocPathTemplate, MethodAwareDocPath, RequiredDoc } from './common';
import { RequestBodyToJsonSchema } from './requests';
@@ -60,12 +59,8 @@ export interface DocRequestMatcher<
| 'delete'
| 'patch'
| 'options'
| 'head' = any,
| 'head',
> {
<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, Method>>(
path: Path,
...handlers: DocRequestHandler<Doc, Path, Method>[]
): T;
<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, Method>>(
path: Path,
...handlers: Array<DocRequestHandler<Doc, Path, Method>>
@@ -74,13 +69,4 @@ export interface DocRequestMatcher<
path: Path,
...handlers: Array<DocRequestHandlerParams<Doc, Path, Method>>
): T;
<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, Method>>(
path: PathParams,
...handlers: Array<DocRequestHandler<Doc, Path, Method>>
): T;
<Path extends MethodAwareDocPath<Doc, DocPathTemplate<Doc>, Method>>(
path: PathParams,
...handlers: Array<DocRequestHandlerParams<Doc, Path, Method>>
): T;
(path: PathParams, subApplication: Application): T;
}
@@ -29,9 +29,7 @@ import type {
DocPathMethod,
DocPathTemplate,
PathTemplate,
ConvertAll,
TuplifyUnion,
ValueOf,
ToTypeSafe,
} from './common';
type RequestBody<
@@ -54,12 +52,10 @@ export type RequestBodySchema<
Method extends DocPathMethod<Doc, Path>,
> = RequestBody<Doc, DocPath<Doc, Path>, Method> extends RequestBodyObject
? ObjectWithContentSchema<Doc, RequestBody<Doc, DocPath<Doc, Path>, Method>>
: unknown;
: never;
export type RequestBodyToJsonSchema<
Doc extends RequiredDoc,
Path extends PathTemplate<Extract<keyof Doc['paths'], string>>,
Method extends DocPathMethod<Doc, Path>,
> = ConvertAll<
TuplifyUnion<ValueOf<RequestBodySchema<Doc, Path, Method>>>
>[number];
> = ToTypeSafe<RequestBodySchema<Doc, Path, Method>>;
@@ -29,9 +29,8 @@ import type {
DocPathMethod,
DocPathTemplate,
PathTemplate,
ConvertAll,
ToTypeSafe,
ValueOf,
TuplifyUnion,
} from './common';
type Response<
@@ -85,13 +84,11 @@ export type ResponseSchemas<
Doc,
Responses<Doc, DocPath<Doc, Path>, Method>[StatusCode]
>
: unknown;
: never;
};
export type ResponseBodyToJsonSchema<
Doc extends RequiredDoc,
Path extends PathTemplate<Extract<keyof Doc['paths'], string>>,
Method extends DocPathMethod<Doc, Path>,
> = ConvertAll<
TuplifyUnion<ValueOf<ResponseSchemas<Doc, Path, Method>>>
>[number];
> = ToTypeSafe<ValueOf<ResponseSchemas<Doc, Path, Method>>>;