Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## unreleased

- CLI: Fix YAML output to preserve x-version number formatting
- Casing - Configure characters to keep

## [1.31.0] - 2026-04-12

Expand Down
101 changes: 68 additions & 33 deletions openapi-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -889,23 +889,24 @@ async function openapiChangeCase(oaObj, options) {
let jsonObj = JSON.parse(JSON.stringify(oaObj)); // Deep copy of the schema object
let defaultCasing = {}; // JSON.parse(fs.readFileSync(__dirname + "/defaultFilter.json", 'utf8'))
let casingSet = Object.assign({}, defaultCasing, options.casingSet);
const getKeepChars = target => casingSet[`${target}KeepChars`];

let debugCasingStep = ''; // uncomment // debugCasingStep below to see which sort part is triggered

// Could add a default for all types pretty easily.
const changeCasingKeyPlans = {
query: casingSet.componentsParametersQuery,
path: casingSet.componentsParametersPath,
header: casingSet.componentsParametersHeader,
cookie: casingSet.componentsParametersCookie
query: {case: casingSet.componentsParametersQuery, keep: getKeepChars('componentsParametersQuery')},
path: {case: casingSet.componentsParametersPath, keep: getKeepChars('componentsParametersPath')},
header: {case: casingSet.componentsParametersHeader, keep: getKeepChars('componentsParametersHeader')},
cookie: {case: casingSet.componentsParametersCookie, keep: getKeepChars('componentsParametersCookie')}
};

// Could add a default for all types pretty easily.
const changeCasingNamePlans = {
query: casingSet.parametersQuery,
path: casingSet.parametersPath,
header: casingSet.parametersHeader,
cookie: casingSet.parametersCookie
query: {case: casingSet.parametersQuery, keep: getKeepChars('parametersQuery')},
path: {case: casingSet.parametersPath, keep: getKeepChars('parametersPath')},
header: {case: casingSet.parametersHeader, keep: getKeepChars('parametersHeader')},
cookie: {case: casingSet.parametersCookie, keep: getKeepChars('parametersCookie')}
};

// Initiate components tracking
Expand All @@ -920,17 +921,17 @@ async function openapiChangeCase(oaObj, options) {
// Change components/schemas - names
if (this.path[1] === 'schemas' && this.path.length === 2 && casingSet.componentsSchemas) {
// debugCasingStep = 'Casing - components/schemas - names'
this.update(changeObjKeysCase(node, casingSet.componentsSchemas));
this.update(changeObjKeysCase(node, casingSet.componentsSchemas, getKeepChars('componentsSchemas')));
}
// Change components/examples - names
if (this.path[1] === 'examples' && this.path.length === 2 && casingSet.componentsExamples) {
// debugCasingStep = 'Casing - components/examples - names'
this.update(changeObjKeysCase(node, casingSet.componentsExamples));
this.update(changeObjKeysCase(node, casingSet.componentsExamples, getKeepChars('componentsExamples')));
}
// Change components/headers - names
if (this.path[1] === 'headers' && this.path.length === 2 && casingSet.componentsHeaders) {
// debugCasingStep = 'Casing - components/headers - names'
this.update(changeObjKeysCase(node, casingSet.componentsHeaders));
this.update(changeObjKeysCase(node, casingSet.componentsHeaders, getKeepChars('componentsHeaders')));
}
// Change components/parameters - in:query/in:headers/in:path/in:cookie - key
if (
Expand All @@ -945,7 +946,7 @@ async function openapiChangeCase(oaObj, options) {
const changeCasingKeyPlan = changeCasingKeyPlans[parameterFoundIn];
if (changeCasingKeyPlan) {
// debugCasingStep = `Casing - components/parameters - in:${parameterFoundIn} - key`
const newKey = changeCase(key, changeCasingKeyPlan);
const newKey = changeCase(key, changeCasingKeyPlan.case, changeCasingKeyPlan.keep);
comps.parameters[key] = newKey;
return {[newKey]: orgObj[key]};
}
Expand All @@ -957,27 +958,31 @@ async function openapiChangeCase(oaObj, options) {
if (this.path[1] === 'parameters' && this.path.length === 3) {
if (node.in && changeCasingNamePlans.hasOwnProperty(node.in)) {
const changeCasingNamePlan = changeCasingNamePlans[node.in];
if (changeCasingNamePlan) {
if (changeCasingNamePlan.case) {
// debugCasingStep = `Casing - path > parameters/${node.in} - name`
node.name = changeCase(node.name, changeCasingNamePlan);
node.name = changeCase(node.name, changeCasingNamePlan.case, changeCasingNamePlan.keep);
this.update(node);
}
}
}
// Change components/responses - names
if (this.path[1] === 'responses' && this.path.length === 2 && casingSet.componentsResponses) {
// debugCasingStep = 'Casing - components/responses - names'
this.update(changeObjKeysCase(node, casingSet.componentsResponses));
this.update(changeObjKeysCase(node, casingSet.componentsResponses, getKeepChars('componentsResponses')));
}
// Change components/requestBodies - names
if (this.path[1] === 'requestBodies' && this.path.length === 2 && casingSet.componentsRequestBodies) {
// debugCasingStep = 'Casing - components/requestBodies - names'
this.update(changeObjKeysCase(node, casingSet.componentsRequestBodies));
this.update(
changeObjKeysCase(node, casingSet.componentsRequestBodies, getKeepChars('componentsRequestBodies'))
);
}
// Change components/securitySchemes - names
if (this.path[1] === 'securitySchemes' && this.path.length === 2 && casingSet.componentsSecuritySchemes) {
// debugCasingStep = 'Casing - components/securitySchemes - names'
this.update(changeObjKeysCase(node, casingSet.componentsSecuritySchemes));
this.update(
changeObjKeysCase(node, casingSet.componentsSecuritySchemes, getKeepChars('componentsSecuritySchemes'))
);
}
}
});
Expand All @@ -988,15 +993,29 @@ async function openapiChangeCase(oaObj, options) {
if (this.key === '$ref') {
if (node.startsWith('#/components/schemas/') && casingSet.componentsSchemas) {
const compName = node.replace('#/components/schemas/', '');
this.update(`#/components/schemas/${changeCase(compName, casingSet.componentsSchemas)}`);
this.update(
`#/components/schemas/${changeCase(compName, casingSet.componentsSchemas, getKeepChars('componentsSchemas'))}`
);
}
if (node.startsWith('#/components/examples/') && casingSet.componentsExamples) {
const compName = node.replace('#/components/examples/', '');
this.update(`#/components/examples/${changeCase(compName, casingSet.componentsExamples)}`);
this.update(
`#/components/examples/${changeCase(
compName,
casingSet.componentsExamples,
getKeepChars('componentsExamples')
)}`
);
}
if (node.startsWith('#/components/responses/') && casingSet.componentsResponses) {
const compName = node.replace('#/components/responses/', '');
this.update(`#/components/responses/${changeCase(compName, casingSet.componentsResponses)}`);
this.update(
`#/components/responses/${changeCase(
compName,
casingSet.componentsResponses,
getKeepChars('componentsResponses')
)}`
);
}
if (node.startsWith('#/components/parameters/')) {
const compName = node.replace('#/components/parameters/', '');
Expand All @@ -1006,37 +1025,51 @@ async function openapiChangeCase(oaObj, options) {
}
if (node.startsWith('#/components/headers/') && casingSet.componentsHeaders) {
const compName = node.replace('#/components/headers/', '');
this.update(`#/components/headers/${changeCase(compName, casingSet.componentsHeaders)}`);
this.update(
`#/components/headers/${changeCase(compName, casingSet.componentsHeaders, getKeepChars('componentsHeaders'))}`
);
}
if (node.startsWith('#/components/requestBodies/') && casingSet.componentsRequestBodies) {
const compName = node.replace('#/components/requestBodies/', '');
this.update(`#/components/requestBodies/${changeCase(compName, casingSet.componentsRequestBodies)}`);
this.update(
`#/components/requestBodies/${changeCase(
compName,
casingSet.componentsRequestBodies,
getKeepChars('componentsRequestBodies')
)}`
);
}
if (node.startsWith('#/components/securitySchemes/') && casingSet.componentsSecuritySchemes) {
const compName = node.replace('#/components/securitySchemes/', '');
this.update(`#/components/securitySchemes/${changeCase(compName, casingSet.componentsSecuritySchemes)}`);
this.update(
`#/components/securitySchemes/${changeCase(
compName,
casingSet.componentsSecuritySchemes,
getKeepChars('componentsSecuritySchemes')
)}`
);
}
}

// Change operationId
if (this.key === 'operationId' && casingSet.operationId && this.path[0] === 'paths' && this.path.length === 4) {
// debugCasingStep = 'Casing - Single field - OperationId'
this.update(changeCase(node, casingSet.operationId));
this.update(changeCase(node, casingSet.operationId, getKeepChars('operationId')));
}
// Change summary
if (this.key === 'summary' && casingSet.summary) {
// debugCasingStep = 'Casing - Single field - summary'
this.update(changeCase(node, casingSet.summary));
this.update(changeCase(node, casingSet.summary, getKeepChars('summary')));
}
// Change description
if (this.key === 'description' && casingSet.description) {
// debugCasingStep = 'Casing - Single field - description'
this.update(changeCase(node, casingSet.description));
this.update(changeCase(node, casingSet.description, getKeepChars('description')));
}
// Change paths > examples - name
if (this.path[0] === 'paths' && this.key === 'examples' && casingSet.componentsExamples) {
// debugCasingStep = 'Casing - Single field - examples name'
this.update(changeObjKeysCase(node, casingSet.componentsExamples));
this.update(changeObjKeysCase(node, casingSet.componentsExamples, getKeepChars('componentsExamples')));
}
// Change components/schemas - properties
if (
Expand All @@ -1048,12 +1081,12 @@ async function openapiChangeCase(oaObj, options) {
this.parent.key !== 'value'
) {
// debugCasingStep = 'Casing - components/schemas - properties name'
this.update(changeObjKeysCase(node, casingSet.properties));
this.update(changeObjKeysCase(node, casingSet.properties, getKeepChars('properties')));
}
// Change components/schemas - required properties
if (this.path[1] === 'schemas' && this.parent.key === 'required' && casingSet.properties) {
// debugCasingStep = 'Casing - components/schemas - required properties'
this.update(changeCase(node, casingSet.properties));
this.update(changeCase(node, casingSet.properties, getKeepChars('properties')));
}
// Change paths > schema - properties
if (
Expand All @@ -1065,12 +1098,14 @@ async function openapiChangeCase(oaObj, options) {
this.parent.key !== 'value'
) {
// debugCasingStep = 'Casing - paths > schema - properties name'
this.update(changeObjKeysCase(node, casingSet.properties));
this.update(changeObjKeysCase(node, casingSet.properties, getKeepChars('properties')));
}
// Change security - keys
if (this.path[0] === 'paths' && this.key === 'security' && isArray(node) && casingSet.componentsSecuritySchemes) {
// debugCasingStep = 'Casing - path > - security'
this.update(changeArrayObjKeysCase(node, casingSet.componentsSecuritySchemes));
this.update(
changeArrayObjKeysCase(node, casingSet.componentsSecuritySchemes, getKeepChars('componentsSecuritySchemes'))
);
}
// Change parameters - name
if (this.path[0] === 'paths' && this.key === 'parameters' && changeParametersCasingEnabled(casingSet)) {
Expand All @@ -1081,9 +1116,9 @@ async function openapiChangeCase(oaObj, options) {
for (let i = 0; i < params.length; i++) {
if (params[i].in && changeCasingNamePlans.hasOwnProperty(params[i].in)) {
const changeCasingNamePlan = changeCasingNamePlans[params[i].in];
if (changeCasingNamePlan) {
if (changeCasingNamePlan.case) {
// debugCasingStep = 'Casing - path > parameters/query- name'
params[i].name = changeCase(params[i].name, changeCasingNamePlan);
params[i].name = changeCase(params[i].name, changeCasingNamePlan.case, changeCasingNamePlan.keep);
}
}
}
Expand Down
16 changes: 14 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -1327,11 +1327,17 @@ In this example, the customCasing.yaml file would contain the desired casing pre

```yaml
operationId: snake_case
operationIdKeepChars:
- .
properties: camelCase
propertiesKeepChars:
- .

parametersQuery: kebab-case
parametersHeader: kebab-case
parametersPath: snake_case
parametersQueryKeepChars:
- .

componentsExamples: PascalCase
componentsSchemas: camelCase
Expand All @@ -1343,14 +1349,20 @@ componentsSecuritySchemes: PascalCase
componentsParametersQuery: snake_case
componentsParametersHeader: kebab-case
componentsParametersPath: camelCase
componentsParametersQueryKeepChars:
- .
```

**Casing Options:**
In the customCasing.yaml, you can define the casing style for various OpenAPI properties, allowing you to customize the appearance of your document consistently.

- `operationId`: Defines the casing for operation IDs. Example: snake_case, PascalCase, or camelCase.
- `operationIdKeepChars`: Keeps selected characters while casing `operationId`. Example: `.` for dotted identifiers.
- `properties`: Sets the casing for properties within components. Example: camelCase.
- `parametersQuery`, `parametersHeader`, `parametersPath`: Define different casing styles for parameters based on their location (query, header, path). Example: snake_case, kebab-case.
- `propertiesKeepChars`: Keeps selected characters while casing properties. Example: `.`.
- `parametersQuery`, `parametersHeader`, `parametersPath`, `parametersCookie`: Define different casing styles for parameters based on their location (query, header, path, cookie). Example: snake_case, kebab-case.
- `parametersQueryKeepChars`, `parametersHeaderKeepChars`, `parametersPathKeepChars`, `parametersCookieKeepChars`: Keep selected characters while casing inline parameters.
- `componentsParametersQueryKeepChars`, `componentsParametersHeaderKeepChars`, `componentsParametersPathKeepChars`, `componentsParametersCookieKeepChars`: Keep selected characters while casing referenced parameters in `components.parameters`.
- and many more

See [OpenAPI formatting configuration options](#openapi-formatting-configuration-options) for the full list of casing options
Expand Down Expand Up @@ -1713,4 +1725,4 @@ The casing options available in `openapi-format` are powered by the excellent [c
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.png" alt="JetBrains logo." width="200px">
</a>

Special thanks to [JetBrains](https://www.jetbrains.com/) for their continuous sponsorship of this project over the last 3 years, and for their support to open-source software (OSS) initiatives.
Special thanks to [JetBrains](https://www.jetbrains.com/) for their continuous sponsorship of this project over the last 3 years, and for their support to open-source software (OSS) initiatives.
Loading
Loading