From 8b8ea50e18eae564c6c3792fe167054276f89be1 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Fri, 17 May 2024 15:21:50 -0700 Subject: [PATCH] rename --- __tests__/__snapshots__/patch.test.ts.snap | 214 +++++++++++++++++++++ __tests__/path.rename-property.test.ts | 64 ++++++ src/index.ts | 32 ++- 3 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 __tests__/path.rename-property.test.ts diff --git a/__tests__/__snapshots__/patch.test.ts.snap b/__tests__/__snapshots__/patch.test.ts.snap index 80dfc23..5c9a1be 100644 --- a/__tests__/__snapshots__/patch.test.ts.snap +++ b/__tests__/__snapshots__/patch.test.ts.snap @@ -3,6 +3,217 @@ exports[`JSONSchemaPatch Operations should correctly apply schema modifications and match snapshot 1`] = ` { "$defs": { + "asset": { + "additionalProperties": false, + "if": { + "properties": { + "type_asset": { + "enum": [ + "erc20", + "cw20", + "snip20", + ], + }, + }, + "required": [ + "type_asset", + ], + }, + "properties": { + "address": { + "description": "[OPTIONAL] The address of the asset. Only required for type_asset : cw20, snip20", + "type": "string", + }, + "base": { + "description": "The base unit of the asset. Must be in denom_units.", + "type": "string", + }, + "coingecko_id": { + "description": "[OPTIONAL] The coingecko id to fetch asset data from coingecko v3 api. See https://api.coingecko.com/api/v3/coins/list", + "type": "string", + }, + "denom_units": { + "items": { + "$ref": "#/$defs/denom_unit", + }, + "minContains": 1, + "type": "array", + }, + "deprecated": { + "description": "[OPTIONAL] Whether the asset has been deprecated for use. For readability, it is best to omit this property unless TRUE.", + "type": "boolean", + }, + "description": { + "description": "[OPTIONAL] A short description of the asset", + "type": "string", + }, + "display": { + "description": "The human friendly unit of the asset. Must be in denom_units.", + "type": "string", + }, + "extended_description": { + "description": "[OPTIONAL] A long description of the asset", + "type": "string", + }, + "ibc": { + "additionalProperties": false, + "description": "[OPTIONAL] IBC Channel between src and dst between chain", + "properties": { + "dst_channel": { + "type": "string", + }, + "source_channel": { + "type": "string", + }, + "source_denom": { + "type": "string", + }, + }, + "required": [ + "source_channel", + "dst_channel", + "source_denom", + ], + "type": "object", + }, + "images": { + "items": { + "additionalProperties": false, + "properties": { + "image_sync": { + "$ref": "#/$defs/pointer", + }, + "png": { + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$", + "type": "string", + }, + "svg": { + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$", + "type": "string", + }, + "theme": { + "additionalProperties": false, + "minProperties": 1, + "properties": { + "circle": { + "type": "boolean", + }, + "dark_mode": { + "type": "boolean", + }, + "primary_color_hex": { + "pattern": "^#[0-9a-fA-F]{6}$", + "type": "string", + }, + }, + "type": "object", + }, + }, + "type": "object", + }, + "minItems": 1, + "type": "array", + }, + "keywords": { + "items": { + "type": "string", + }, + "maxContains": 20, + "minContains": 1, + "type": "array", + }, + "logo_URIs": { + "additionalProperties": false, + "properties": { + "png": { + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$", + "type": "string", + }, + "svg": { + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$", + "type": "string", + }, + }, + "type": "object", + }, + "name": { + "description": "The project name of the asset. For example Bitcoin.", + "maxLength": 42, + "type": "string", + }, + "newDescription": { + "description": "A new description property", + "type": "string", + }, + "socials": { + "minProperties": 1, + "properties": { + "twitter": { + "format": "uri", + "type": "string", + }, + "website": { + "format": "uri", + "type": "string", + }, + }, + "type": "object", + }, + "traces": { + "description": "The origin of the asset, starting with the index, and capturing all transitions in form and location.", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/ibc_transition", + }, + { + "$ref": "#/$defs/ibc_cw20_transition", + }, + { + "$ref": "#/$defs/non_ibc_transition", + }, + ], + }, + "minContains": 1, + "type": "array", + }, + "type_asset": { + "default": "sdk.coin", + "description": "[OPTIONAL] The potential options for type of asset. By default, assumes sdk.coin", + "enum": [ + "sdk.coin", + "cw20", + "erc20", + "ics20", + "snip20", + "snip25", + "sdk.factory", + "bitcoin-like", + "evm-base", + "svm-base", + "substrate", + "bitsong", + ], + "type": "string", + }, + }, + "required": [ + "denom_units", + "base", + "display", + "name", + ], + "then": { + "required": [ + "address", + ], + }, + "type": "object", + }, "denom_unit": { "additionalProperties": false, "properties": { @@ -250,6 +461,9 @@ exports[`JSONSchemaPatch Operations should correctly apply schema modifications "type": "string", }, "assets": { + "items": { + "$ref": "#/$defs/asset", + }, "minContains": 1, "type": "array", }, diff --git a/__tests__/path.rename-property.test.ts b/__tests__/path.rename-property.test.ts new file mode 100644 index 0000000..6b2d4ad --- /dev/null +++ b/__tests__/path.rename-property.test.ts @@ -0,0 +1,64 @@ +import JSONSchemaPatch from '../src'; + +describe('JSONSchemaPatch - renameProperty', () => { + it('should rename a property and update required fields', () => { + const schema = { + properties: { + oldName: { type: "string" }, + otherProp: { type: "number" } + }, + required: ["oldName", "otherProp"] + }; + const patcher = new JSONSchemaPatch(schema); + + // Preparing operation to rename 'oldName' to 'newName' + patcher.prepareOperation({ + op: 'renameProperty', + path: '/properties', + value: { oldName: 'oldName', newName: 'newName' } + }); + + // Applying all prepared operations + patcher.applyPatch(); + + // Expected schema after the operation + const expectedSchema = { + properties: { + newName: { type: "string" }, // 'oldName' should now be 'newName' + otherProp: { type: "number" } + }, + required: ["newName", "otherProp"] // 'required' should be updated to reflect the new property name + }; + + // Asserting that the schema has been updated as expected + expect(schema).toEqual(expectedSchema); + }); + + it('should handle renaming a non-existent property gracefully', () => { + const schema = { + properties: { + someProp: { type: "string" } + }, + required: ["someProp"] + }; + const patcher = new JSONSchemaPatch(schema); + + // Attempting to rename a non-existent property + patcher.prepareOperation({ + op: 'renameProperty', + path: '/properties', + value: { oldName: 'nonExistent', newName: 'newName' } + }); + + // Applying all prepared operations + patcher.applyPatch(); + + // Expect the schema to remain unchanged as the property does not exist + expect(schema).toEqual({ + properties: { + someProp: { type: "string" } + }, + required: ["someProp"] + }); + }); +}); diff --git a/src/index.ts b/src/index.ts index 792dfdb..50bcd91 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { applyPatch, Operation } from 'fast-json-patch'; export interface JSONSchemaPatchOperation { - op: 'add' | 'remove' | 'replace' | 'addProperty' | 'removeProperty' | 'addDefinition' | 'removeDefinition'; + op: 'add' | 'remove' | 'replace' | 'addProperty' | 'removeProperty' | 'renameProperty' | 'addDefinition' | 'removeDefinition'; path: string; value?: any; } @@ -37,11 +37,13 @@ export class JSONSchemaPatch { case 'removeProperty': this.removeProperty(op.path, op.value); break; + case 'renameProperty': + this.renameProperty(op.path, op.value.oldName, op.value.newName); case 'addDefinition': this.addDefinition(op.path, op.value); break; case 'removeDefinition': - this.removeDefinition(op.path); + this.removeDefinition(op.value); break; default: // Standard operations will be processed later @@ -135,6 +137,32 @@ export class JSONSchemaPatch { applyPatch(this.schema, [{ op: 'remove', path: `${requiredPath}/${requiredIndex}` }]); } } + + renameProperty(path: string, oldName: string, newName: string): void { + const propertyPath = `${path}/properties/${oldName}`; + const newPath = `${path}/properties/${newName}`; + const propertyValue = getValueAtPath(this.schema, propertyPath); + if (propertyValue) { + applyPatch(this.schema, [{ op: 'remove', path: propertyPath }]); + applyPatch(this.schema, [{ op: 'add', path: newPath, value: propertyValue }]); + this.updateRequiredField(path, oldName, newName); + } + } + + private updateRequiredField(path: string, oldName: string, newName: string): void { + const requiredPath = `${path}/required`; + const requiredArray = getValueAtPath(this.schema, requiredPath); + if (requiredArray && requiredArray.includes(oldName)) { + const index = requiredArray.indexOf(oldName); + if (index !== -1) { + applyPatch(this.schema, [ + { op: 'remove', path: `${requiredPath}/${index}` }, + { op: 'add', path: `${requiredPath}/-`, value: newName } + ]); + } + } + } + } export default JSONSchemaPatch;