Skip to content
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/* ============================================================================
* Copyright (c) Palo Alto Networks
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* ========================================================================== */

import { SchemaObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
import merge from "lodash/merge";

export interface SchemaSelections {
[schemaPath: string]: number;
}

/**
* Resolves a schema by replacing anyOf/oneOf with the selected option based on user selections.
*
* @param schema - The original schema object
* @param selections - Map of schema paths to selected indices
* @param basePath - The base path for this schema (used for looking up selections)
* @returns A new schema with anyOf/oneOf resolved to selected options
*/
export function resolveSchemaWithSelections(
schema: SchemaObject | undefined,
selections: SchemaSelections,
basePath: string = "requestBody"
): SchemaObject | undefined {
if (!schema) {
return schema;
}

// Deep clone to avoid mutating the original schema
const schemaCopy = JSON.parse(JSON.stringify(schema)) as SchemaObject;

return resolveSchemaRecursive(schemaCopy, selections, basePath);
}

function resolveSchemaRecursive(
schema: SchemaObject,
selections: SchemaSelections,
currentPath: string
): SchemaObject {
// Handle oneOf
if (schema.oneOf && Array.isArray(schema.oneOf)) {
const selectedIndex = selections[currentPath] ?? 0;
const selectedSchema = schema.oneOf[selectedIndex] as SchemaObject;

if (selectedSchema) {
// If there are shared properties, merge them with the selected schema
if (schema.properties) {
const mergedSchema = merge({}, schema, selectedSchema);
delete mergedSchema.oneOf;

// Continue resolving nested schemas in the merged result
return resolveSchemaRecursive(
mergedSchema,
selections,
`${currentPath}.${selectedIndex}`
);
}

// No shared properties, just use the selected schema
// Continue resolving in case there are nested anyOf/oneOf
return resolveSchemaRecursive(
selectedSchema,
selections,
`${currentPath}.${selectedIndex}`
);
}
}

// Handle anyOf
if (schema.anyOf && Array.isArray(schema.anyOf)) {
const selectedIndex = selections[currentPath] ?? 0;
const selectedSchema = schema.anyOf[selectedIndex] as SchemaObject;

if (selectedSchema) {
// If there are shared properties, merge them with the selected schema
if (schema.properties) {
const mergedSchema = merge({}, schema, selectedSchema);
delete mergedSchema.anyOf;

// Continue resolving nested schemas in the merged result
return resolveSchemaRecursive(
mergedSchema,
selections,
`${currentPath}.${selectedIndex}`
);
}

// No shared properties, just use the selected schema
// Continue resolving in case there are nested anyOf/oneOf
return resolveSchemaRecursive(
selectedSchema,
selections,
`${currentPath}.${selectedIndex}`
);
}
}

// Handle allOf - merge all schemas and continue resolving
if (schema.allOf && Array.isArray(schema.allOf)) {
// Process each allOf item, resolving any anyOf/oneOf within them
const resolvedItems = schema.allOf.map((item, index) => {
return resolveSchemaRecursive(
item as SchemaObject,
selections,
`${currentPath}.allOf.${index}`
);
});

// Merge all resolved items
const mergedSchema = resolvedItems.reduce(
(acc, item) => merge(acc, item),
{} as SchemaObject
);

// Preserve any top-level properties from the original schema
if (schema.properties) {
mergedSchema.properties = merge(
{},
mergedSchema.properties,
schema.properties
);
}

return mergedSchema;
}

// Handle object properties recursively
if (schema.properties) {
const resolvedProperties: { [key: string]: SchemaObject } = {};

for (const [propName, propSchema] of Object.entries(schema.properties)) {
resolvedProperties[propName] = resolveSchemaRecursive(
propSchema as SchemaObject,
selections,
`${currentPath}.${propName}`
);
}

schema.properties = resolvedProperties;
}

// Handle array items recursively
if (schema.items) {
schema.items = resolveSchemaRecursive(
schema.items as SchemaObject,
selections,
`${currentPath}.items`
);
}

return schema;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* ============================================================================
* Copyright (c) Palo Alto Networks
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* ========================================================================== */

export {
default as schemaSelectionReducer,
setSchemaSelection,
clearSchemaSelections,
} from "./slice";
export type { SchemaSelectionState } from "./slice";
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* ============================================================================
* Copyright (c) Palo Alto Networks
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* ========================================================================== */

import { createSlice, PayloadAction } from "@reduxjs/toolkit";

export interface SchemaSelectionState {
/**
* Maps schema path (e.g., "requestBody", "requestBody.anyOf.0.layer3")
* to the selected anyOf/oneOf option index
*/
selections: { [schemaPath: string]: number };
}

const initialState: SchemaSelectionState = {
selections: {},
};

export const slice = createSlice({
name: "schemaSelection",
initialState,
reducers: {
/**
* Set the selected index for a specific schema path
*/
setSchemaSelection: (
state,
action: PayloadAction<{ path: string; index: number }>
) => {
state.selections[action.payload.path] = action.payload.index;
},
/**
* Clear all schema selections (useful when navigating to a new API endpoint)
*/
clearSchemaSelections: (state) => {
state.selections = {};
},
},
});

export const { setSchemaSelection, clearSchemaSelections } = slice.actions;

export default slice.reducer;
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export default function ApiItem(props: Props): JSX.Element {
body: { type: "empty" },
params,
auth,
schemaSelection: { selections: {} },
},
[persistenceMiddleware]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import body from "@theme/ApiExplorer/Body/slice";
import contentType from "@theme/ApiExplorer/ContentType/slice";
import params from "@theme/ApiExplorer/ParamOptions/slice";
import response from "@theme/ApiExplorer/Response/slice";
import schemaSelection from "@theme/ApiExplorer/SchemaSelection/slice";
import server from "@theme/ApiExplorer/Server/slice";

const rootReducer = combineReducers({
Expand All @@ -22,6 +23,7 @@ const rootReducer = combineReducers({
body,
params,
auth,
schemaSelection,
});

export type RootState = ReturnType<typeof rootReducer>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ const RequestSchemaComponent: React.FC<Props> = ({ title, body, style }) => {
)}
</div>
<ul style={{ marginLeft: "1rem" }}>
<SchemaNode schema={firstBody} schemaType="request" />
<SchemaNode
schema={firstBody}
schemaType="request"
schemaPath="requestBody"
/>
</ul>
</Details>
</div>
Expand Down Expand Up @@ -153,7 +157,11 @@ const RequestSchemaComponent: React.FC<Props> = ({ title, body, style }) => {
)}
</div>
<ul style={{ marginLeft: "1rem" }}>
<SchemaNode schema={firstBody} schemaType="request" />
<SchemaNode
schema={firstBody}
schemaType="request"
schemaPath="requestBody"
/>
</ul>
</Details>
</TabItem>
Expand Down
Loading
Loading