Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encode '/' in prop name #1834

Merged
merged 2 commits into from
Feb 4, 2022
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
11 changes: 7 additions & 4 deletions packages/angular-material/src/other/master-detail/master.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
ArrayControlProps,
ControlElement,
createDefaultValue,
decode,
findUISchema,
getFirstPrimitiveProp,
JsonFormsState,
Expand All @@ -47,10 +48,12 @@ import {
const keywords = ['#', 'properties', 'items'];

export const removeSchemaKeywords = (path: string) => {
return path
.split('/')
.filter(s => !some(keywords, key => key === s))
.join('.');
return decode(
path
.split('/')
.filter(s => !some(keywords, key => key === s))
.join('.')
);
};

@Component({
Expand Down
4 changes: 3 additions & 1 deletion packages/angular-material/src/other/table.renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
ArrayControlProps,
ControlElement,
deriveTypes,
encode,
isObjectArrayControl,
isPrimitiveArrayControl,
JsonSchema,
Expand Down Expand Up @@ -101,7 +102,8 @@ export class TableRenderer extends JsonFormsArrayControl {
): ColumnDescription[] => {
if (schema.type === 'object') {
return this.getValidColumnProps(schema).map(prop => {
const uischema = controlWithoutLabel(`#/properties/${prop}`);
const encProp = encode(prop);
const uischema = controlWithoutLabel(`#/properties/${encProp}`);
if (!this.isEnabled()) {
setReadonly(uischema);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/generators/uischema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
Layout,
UISchemaElement
} from '../models';
import { deriveTypes, resolveSchema } from '../util';
import { deriveTypes, encode, resolveSchema } from '../util';

/**
* Creates a new ILayout.
Expand Down Expand Up @@ -162,7 +162,7 @@ const generateUISchema = (
const nextRef: string = currentRef + '/properties';
Object.keys(jsonSchema.properties).map(propName => {
let value = jsonSchema.properties[propName];
const ref = `${nextRef}/${propName}`;
const ref = `${nextRef}/${encode(propName)}`;
if (value.$ref !== undefined) {
value = resolveSchema(rootSchema, value.$ref);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/util/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import startCase from 'lodash/startCase';

import { ControlElement, JsonSchema, LabelDescription } from '../models';
import { decode } from './path';

const deriveLabel = (
controlElement: ControlElement,
Expand All @@ -36,8 +37,7 @@ const deriveLabel = (
}
if (typeof controlElement.scope === 'string') {
const ref = controlElement.scope;
const label = ref.substr(ref.lastIndexOf('/') + 1);

const label = decode(ref.substr(ref.lastIndexOf('/') + 1));
return startCase(label);
}

Expand Down
17 changes: 15 additions & 2 deletions packages/core/src/util/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ export const toDataPathSegments = (schemaPath: string): string[] => {
.replace(/oneOf\/[\d]\//g, '');
const segments = s.split('/');

const startFromRoot = segments[0] === '#' || segments[0] === '';
const decodedSegments = segments.map(decode);

const startFromRoot = decodedSegments[0] === '#' || decodedSegments[0] === '';
const startIndex = startFromRoot ? 2 : 1;
return range(startIndex, segments.length, 2).map(idx => segments[idx]);
return range(startIndex, decodedSegments.length, 2).map(idx => decodedSegments[idx]);
};

/**
Expand All @@ -88,3 +90,14 @@ export const composeWithUi = (scopableUi: Scopable, path: string): string => {

return isEmpty(segments) ? path : compose(path, segments.join('.'));
};

/**
* Encodes the given segment to be used as part of a JSON Pointer
*
* JSON Pointer has special meaning for "/" and "~", therefore these must be encoded
*/
export const encode = (segment: string) => segment?.replace(/~/g, '~0').replace(/\//g, '~1');
/**
* Decodes a given JSON Pointer segment to its "normal" representation
*/
export const decode = (pointerSegment: string) => pointerSegment?.replace(/~1/g, '/').replace(/~0/, '~');
1 change: 1 addition & 0 deletions packages/core/src/util/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const isRequired = (
): boolean => {
const pathSegments = schemaPath.split('/');
const lastSegment = pathSegments[pathSegments.length - 1];
// Skip "properties", "items" etc. to resolve the parent
const nextHigherSchemaSegments = pathSegments.slice(
0,
pathSegments.length - 2
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/util/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import isEmpty from 'lodash/isEmpty';
import get from 'lodash/get';
import { JsonSchema } from '../models';
import { decode, encode } from './path';

/**
* Map for storing refs and the respective schemas they are pointing to.
Expand Down Expand Up @@ -115,7 +116,7 @@ export const resolveSchema = (
if (isEmpty(schema)) {
return undefined;
}
const validPathSegments = schemaPath.split('/');
const validPathSegments = schemaPath.split('/').map(decode);
let resultSchema = schema;
for (let i = 0; i < validPathSegments.length; i++) {
let pathSegment = validPathSegments[i];
Expand All @@ -136,7 +137,7 @@ export const resolveSchema = (
resultSchema?.anyOf ?? []
);
for (let item of schemas) {
curSchema = resolveSchema(item, validPathSegments.slice(i).join('/'));
curSchema = resolveSchema(item, validPathSegments.slice(i).map(encode).join('/'));
if (curSchema) {
break;
}
Expand Down
18 changes: 18 additions & 0 deletions packages/core/test/generators/uischema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,3 +556,21 @@ test('generate control for nested oneOf', t => {
};
t.deepEqual(generateDefaultUISchema(schema), uischema);
});

test('encode "/" in generated ui schema', t => {
const schema: JsonSchema = {
properties: {
'some / initial / value': {
type : 'integer'
}
}
};
const uischema: Layout = {
type: 'VerticalLayout',
elements: [{
type: 'Control',
scope: '#/properties/some ~1 initial ~1 value'
}] as ControlElement[]
};
t.deepEqual(generateDefaultUISchema(schema), uischema);
});
3 changes: 3 additions & 0 deletions packages/core/test/util/path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ test('toDataPath use of encoded paths relative without /', t => {
const fooBar = encodeURIComponent('foo/bar');
t.is(toDataPath(`properties/${fooBar}`), `${fooBar}`);
});
test('toDataPath use of encoded special character in pathname', t => {
t.is(toDataPath('properties/foo~0bar~1baz'), 'foo~bar/baz');
});
test('resolve instance', t => {
const instance = { foo: 123 };
const result = Resolve.data(instance, toDataPath('#/properties/foo'));
Expand Down
15 changes: 14 additions & 1 deletion packages/core/test/util/resolvers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,17 @@ test('resolveSchema - resolves schema with any ', t => {
t.deepEqual(resolveSchema(schema, '#/properties/description/properties/index'), {type: 'number'});
t.deepEqual(resolveSchema(schema, '#/properties/description/properties/exist'), {type: 'boolean'});
t.is(resolveSchema(schema, '#/properties/description/properties/notfound'), undefined);
});
});

test('resolveSchema - resolves schema with encoded characters', t => {
const schema = {
type: 'object',
properties: {
'foo / ~ bar': {
type: 'integer'
}
}
};
t.deepEqual(resolveSchema(schema, '#/properties/foo ~1 ~0 bar'), {type: 'integer'});
t.is(resolveSchema(schema, '#/properties/foo / bar'), undefined);
});
10 changes: 5 additions & 5 deletions packages/material/src/complex/MaterialTableControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ import {
Paths,
Resolve,
JsonFormsRendererRegistryEntry,
JsonFormsCellRendererRegistryEntry
JsonFormsCellRendererRegistryEntry,
encode
} from '@jsonforms/core';
import DeleteIcon from '@mui/icons-material/Delete';
import ArrowDownward from '@mui/icons-material/ArrowDownward';
Expand Down Expand Up @@ -206,18 +207,17 @@ interface NonEmptyCellComponentProps {
cells?: JsonFormsCellRendererRegistryEntry[],
isValid: boolean
}
const NonEmptyCellComponent = React.memo(({path, propName, schema,rootSchema, errors, enabled, renderers, cells, isValid}:NonEmptyCellComponentProps) => {

const NonEmptyCellComponent = React.memo(({path, propName, schema, rootSchema, errors, enabled, renderers, cells, isValid}:NonEmptyCellComponentProps) => {
return (
<NoBorderTableCell>
{schema.properties ? (
<DispatchCell
schema={Resolve.schema(
schema,
`#/properties/${propName}`,
`#/properties/${encode(propName)}`,
rootSchema
)}
uischema={controlWithoutLabel(`#/properties/${propName}`)}
uischema={controlWithoutLabel(`#/properties/${encode(propName)}`)}
path={path}
enabled={enabled}
renderers={renderers}
Expand Down
8 changes: 4 additions & 4 deletions packages/vanilla/src/complex/TableArrayControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import {
RankedTester,
Resolve,
Test,
getControlPath
getControlPath,
encode
} from '@jsonforms/core';
import { DispatchCell, withJsonFormsArrayControlProps } from '@jsonforms/react';
import { withVanillaControlProps } from '../util';
Expand Down Expand Up @@ -167,12 +168,11 @@ class TableArrayControl extends React.Component<ArrayControlProps & VanillaRende
childPath,
prop.toString()
);

return (
<td key={childPropPath}>
<DispatchCell
schema={Resolve.schema(schema, `#/properties/${prop}`, rootSchema)}
uischema={createControlElement(prop)}
schema={Resolve.schema(schema, `#/properties/${encode(prop)}`, rootSchema)}
uischema={createControlElement(encode(prop))}
path={childPath + '.' + prop}
/>
</td>
Expand Down