Skip to content

Commit

Permalink
Merge branch 'main' into rk/text-editor-ui-design
Browse files Browse the repository at this point in the history
  • Loading branch information
ryankeairns authored Jun 18, 2024
2 parents 25dac89 + f93f04a commit 3f51f5a
Show file tree
Hide file tree
Showing 126 changed files with 3,599 additions and 2,776 deletions.
2 changes: 1 addition & 1 deletion fleet_packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
{
"name": "elastic_agent",
"version": "1.19.2"
"version": "1.20.0"
},
{
"name": "endpoint",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,7 @@
"mochawesome-merge": "^4.3.0",
"mock-fs": "^5.1.2",
"ms-chromium-edge-driver": "^0.5.1",
"msw": "^2.3.1",
"multistream": "^4.1.0",
"mutation-observer": "^1.0.3",
"native-hdr-histogram": "^1.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-openapi-generator/src/openapi_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export const generate = async (config: GeneratorConfig) => {
version: 'Bundle (no version)',
},
imports: {},
circularRefs: new Set<string>(),
});

await fs.writeFile(bundle.outFile, result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import { getImportsMap, ImportsMap } from './lib/get_imports_map';
import { normalizeSchema } from './lib/normalize_schema';
import { NormalizedOperation, OpenApiDocument } from './openapi_types';
import { getInfo } from './lib/get_info';
import { getCircularRefs } from './lib/get_circular_refs';

export interface GenerationContext {
components: OpenAPIV3.ComponentsObject | undefined;
operations: NormalizedOperation[];
info: OpenAPIV3.InfoObject;
imports: ImportsMap;
circularRefs: Set<string>;
}

export function getGenerationContext(document: OpenApiDocument): GenerationContext {
Expand All @@ -28,11 +30,13 @@ export function getGenerationContext(document: OpenApiDocument): GenerationConte
const operations = getApiOperationsList(normalizedDocument);
const info = getInfo(normalizedDocument);
const imports = getImportsMap(normalizedDocument);
const circularRefs = getCircularRefs(normalizedDocument);

return {
components,
operations,
info,
imports,
circularRefs,
};
}
28 changes: 28 additions & 0 deletions packages/kbn-openapi-generator/src/parser/lib/find_refs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { hasRef } from './helpers/has_ref';
import { traverseObject } from './helpers/traverse_object';

/**
* Traverse the OpenAPI document recursively and find all references
*
* @param obj Any object
* @returns A list of external references
*/
export function findRefs(obj: unknown): string[] {
const refs: string[] = [];

traverseObject(obj, (element) => {
if (hasRef(element)) {
refs.push(element.$ref);
}
});

return refs;
}
105 changes: 105 additions & 0 deletions packages/kbn-openapi-generator/src/parser/lib/get_circular_refs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { OpenApiDocument } from '../openapi_types';
import type { PlainObject } from './helpers/plain_object';
import { extractByJsonPointer } from './helpers/extract_by_json_pointer';
import { findRefs } from './find_refs';

/**
* Extracts circular references from a provided document.
* Currently only local references are supported.
*/
export function getCircularRefs(document: OpenApiDocument): Set<string> {
const localRefs = findLocalRefs(document);
const circularRefs = new Set<string>();
const resolveLocalRef = (localRef: string): PlainObject =>
extractByJsonPointer(document, extractJsonPointer(localRef));

// In general references represent a disconnected graph. To find
// all references cycles we need to check each reference.
for (const startRef of new Set(localRefs)) {
const cycleHeadRef = findCycleHeadRef(startRef, resolveLocalRef);

if (cycleHeadRef) {
circularRefs.add(cycleHeadRef);
}
}

return circularRefs;
}

/**
* Searches for a cycle head. A search starts from `startRef` reference.
*
* A cycle head is a first ref in a cycle. If `startRef` inside a cycle
* a cycle head is the starting ref. It can be illustrated as
*
* c1 - c2 - c3
* / |
* r1 -> r2 -> r3 -> head c4
* \ |
* c7 - c6 - c5
*
* On the schema above references `r1`, `r2` and `r3` depend on the cycle but
* aren't part of the cycle. When search is started from them `head` is
* returned. If a search starts from `c3` then `c3` will be returned.
*
* @param startRef A starting point to find a cycle
* @param resolveRef A callback function to resolve an encountered reference.
* It should return a document node the provided ref resolves to.
* @returns a Set representing a cycle or an empty set if a cycle is not found
*/
function findCycleHeadRef(
startRef: string,
resolveRef: (ref: string) => PlainObject
): string | undefined {
let result: string | undefined;

const visitedRefs = new Set<string>();
const search = (ref: string): void => {
if (visitedRefs.has(ref)) {
result = ref;
return;
}

const refNode = resolveRef(ref);
const nextRefs = findLocalRefs(refNode);

visitedRefs.add(ref);
nextRefs.forEach(search);
visitedRefs.delete(ref);
};

search(startRef);

return result;
}

/**
* Finds local references
*/
function findLocalRefs(obj: unknown): string[] {
return findRefs(obj).filter((ref) => isLocalRef(ref));
}

/**
* Checks whether the provided ref is local.
* Local references start with `#/`
*/
function isLocalRef(ref: string): boolean {
return ref.startsWith('#/');
}

/**
* Extracts a JSON Pointer from a local reference
* by getting rid of the leading slash
*/
function extractJsonPointer(ref: string): string {
return ref.substring(1);
}
30 changes: 1 addition & 29 deletions packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { uniq } from 'lodash';
import type { OpenApiDocument } from '../openapi_types';
import { traverseObject } from './traverse_object';
import { findRefs } from './find_refs';

export interface ImportsMap {
[importPath: string]: string[];
Expand Down Expand Up @@ -37,31 +37,3 @@ export const getImportsMap = (parsedSchema: OpenApiDocument): ImportsMap => {

return importMap;
};

/**
* Check if an object has a $ref property
*
* @param obj Any object
* @returns True if the object has a $ref property
*/
const hasRef = (obj: unknown): obj is { $ref: string } => {
return typeof obj === 'object' && obj !== null && '$ref' in obj;
};

/**
* Traverse the OpenAPI document recursively and find all references
*
* @param obj Any object
* @returns A list of external references
*/
function findRefs(obj: unknown): string[] {
const refs: string[] = [];

traverseObject(obj, (element) => {
if (hasRef(element)) {
refs.push(element.$ref);
}
});

return refs;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { PlainObject } from './plain_object';
import { isPlainObjectType } from './is_plain_object_type';

/**
* Extract a node from a document using a provided [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901).
*
* JSON Pointer is the second part in [JSON Reference](https://datatracker.ietf.org/doc/html/draft-pbryan-zyp-json-ref-03).
* For example an object `{ $ref: "./some-file.yaml#/components/schemas/MySchema"}` is a reference node.
* Where `/components/schemas/MySchema` is a JSON pointer. `./some-file.yaml` is a document reference.
* Yaml shares the same JSON reference standard and basically can be considered just as a different
* JS Object serialization format. See OpenAPI [Using $ref](https://swagger.io/docs/specification/using-ref/) for more information.
*
* @param document a document containing node to resolve by using the pointer
* @param pointer a JSON Pointer
* @returns resolved document node (it's always a JS object)
*/
export function extractByJsonPointer(document: unknown, pointer: string): PlainObject {
if (!pointer.startsWith('/')) {
throw new Error('$ref pointer must start with a leading slash');
}

if (!isPlainObjectType(document)) {
throw new Error('document must be an object');
}

let target = document;

for (const segment of pointer.slice(1).split('/')) {
const nextTarget = target[segment];

if (!isPlainObjectType(nextTarget)) {
throw new Error(`JSON Pointer "${pointer}" is not found in "${JSON.stringify(document)}"`);
}

target = nextTarget;
}

return target;
}
19 changes: 19 additions & 0 deletions packages/kbn-openapi-generator/src/parser/lib/helpers/has_ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { NormalizedReferenceObject } from '../../openapi_types';

/**
* Check if an object has a $ref property
*
* @param obj Any object
* @returns True if the object has a $ref property
*/
export function hasRef(obj: unknown): obj is NormalizedReferenceObject {
return typeof obj === 'object' && obj !== null && '$ref' in obj;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { isPlainObject } from 'lodash';
import type { PlainObject } from './plain_object';

export function isPlainObjectType(maybeObj: unknown): maybeObj is PlainObject {
return isPlainObject(maybeObj);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export type PlainObject = Record<string, unknown>;
56 changes: 23 additions & 33 deletions packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,31 @@
*/

import { OpenAPIV3 } from 'openapi-types';
import { NormalizedReferenceObject } from '../openapi_types';
import { traverseObject } from './traverse_object';

/**
* Check if an object has a $ref property
*
* @param obj Any object
* @returns True if the object has a $ref property
*/
const hasRef = (obj: unknown): obj is NormalizedReferenceObject => {
return typeof obj === 'object' && obj !== null && '$ref' in obj;
};

const stringIsUrl = (str: string) => {
try {
new URL(str);
return true;
} catch {
return false;
}
};

export function normalizeSchema(schema: OpenAPIV3.Document) {
import { URL } from 'node:url';
import { traverseObject } from './helpers/traverse_object';
import { hasRef } from './helpers/has_ref';

function isUrl(maybeUrl: string): boolean {
return URL.canParse(maybeUrl);
}

export function normalizeSchema(schema: OpenAPIV3.Document): OpenAPIV3.Document {
traverseObject(schema, (element) => {
if (hasRef(element)) {
if (stringIsUrl(element.$ref)) {
throw new Error(`URL references are not supported: ${element.$ref}`);
}
const referenceName = element.$ref.split('/').pop();
if (!referenceName) {
throw new Error(`Cannot parse reference name: ${element.$ref}`);
}

element.referenceName = referenceName;
if (!hasRef(element)) {
return;
}

if (isUrl(element.$ref)) {
throw new Error(`URL references are not supported: ${element.$ref}`);
}

const referenceName = element.$ref.split('/').pop();

if (!referenceName) {
throw new Error(`Cannot parse reference name: ${element.$ref}`);
}

element.referenceName = referenceName;
});

return schema;
Expand Down
Loading

0 comments on commit 3f51f5a

Please sign in to comment.