diff --git a/package.json b/package.json index 491c203..04638a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@earthranger/react-native-jsonforms-formatter", - "version": "2.0.0-beta.5", + "version": "2.0.0-beta.8", "description": "Converts JTD into JSON Schema ", "main": "./dist/bundle.js", "types": "./dist/index.d.ts", diff --git a/src/common/types.ts b/src/common/types.ts index 24cafa6..d58b891 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -40,7 +40,7 @@ export interface V2BaseProperty { minimum?: number; maximum?: number; format?: 'date-time' | 'date' | 'time' | 'uri'; - anyOf?: Array<{ $ref: string }>; + anyOf?: Array<{ $ref: string } | { oneOf: Array<{ const: any; title?: string }> }>; items?: V2BaseProperty; properties?: Record; required?: string[]; diff --git a/src/v2/generateUISchema.ts b/src/v2/generateUISchema.ts index 43698ab..dd7bb7e 100644 --- a/src/v2/generateUISchema.ts +++ b/src/v2/generateUISchema.ts @@ -11,6 +11,60 @@ import { groupFieldsBySection } from './utils'; +/** + * Validates V2 schema structure for known issues + */ +const validateV2Schema = (schema: V2Schema): void => { + const invalidFields: string[] = []; + const supportedFieldTypes = ['TEXT', 'NUMERIC', 'DATE_TIME', 'CHOICE_LIST', 'LOCATION', 'COLLECTION', 'ATTACHMENT']; + + // Check each field for validity + Object.entries(schema.json.properties).forEach(([fieldName, property]) => { + const uiField = schema.ui.fields[fieldName]; + + // Skip deprecated fields without UI definitions (they won't be rendered anyway) + if (!uiField && property.deprecated) { + return; + } + + if (!uiField) { + invalidFields.push(`${fieldName}: missing UI field definition`); + return; + } + + // Check for unsupported field types + if (!supportedFieldTypes.includes(uiField.type)) { + invalidFields.push(`${fieldName}: unsupported field type '${uiField.type}'`); + return; + } + + // Check CHOICE_LIST fields for valid structure (not content) + if (uiField.type === 'CHOICE_LIST') { + let hasValidStructure = false; + + if (property.type === 'array' && property.items?.anyOf) { + // Check for oneOf arrays in anyOf items for array types (no $ref support) + hasValidStructure = property.items.anyOf.some((anyOfItem: any) => + anyOfItem.oneOf && Array.isArray(anyOfItem.oneOf) // Empty arrays are valid + ); + } else if (property.anyOf) { + // Check direct anyOf structure for string types (no $ref support) + hasValidStructure = property.anyOf.some((anyOfItem: any) => + anyOfItem.oneOf && Array.isArray(anyOfItem.oneOf) // Empty arrays are valid + ); + } + + if (!hasValidStructure) { + invalidFields.push(`${fieldName}: CHOICE_LIST field requires embedded oneOf arrays - $ref not supported`); + } + } + }); + + if (invalidFields.length > 0) { + throw new Error(`Invalid V2 schema structure: ${invalidFields.join(', ')}`); + } +}; + /** * Generates a JSONForms-compatible UI schema from a EarthRanger V2 schema format * @@ -21,8 +75,12 @@ import { * * @param schema - EarthRanger V2 schema with json and ui properties * @returns JSONForms UI schema with single-column VerticalLayout for React Native + * @throws Error if schema has invalid structure or unsupported field configurations */ export const generateUISchema = (schema: V2Schema): JSONFormsUISchema => { + // Validate schema structure first + validateV2Schema(schema); + // Get all visible (non-deprecated) fields const visibleFields = getVisibleFields(schema); diff --git a/src/v2/mockData.ts b/src/v2/mockData.ts index 4ca477f..86bbd33 100644 --- a/src/v2/mockData.ts +++ b/src/v2/mockData.ts @@ -29,7 +29,11 @@ export const mockV2Schema = { "type": "string", "anyOf": [ { - "$ref": "http://localhost:3000/api/v2.0/schemas/choices.json?field=patrol_activity" + "oneOf": [ + { "const": "routine", "title": "Routine Patrol" }, + { "const": "emergency", "title": "Emergency Response" }, + { "const": "investigation", "title": "Investigation" } + ] } ] }, @@ -84,7 +88,12 @@ export const mockV2Schema = { "type": "string", "anyOf": [ { - "$ref": "http://localhost:3000/api/v2.0/schemas/choices.json?field=item_condition" + "oneOf": [ + { "const": "excellent", "title": "Excellent" }, + { "const": "good", "title": "Good" }, + { "const": "fair", "title": "Fair" }, + { "const": "poor", "title": "Poor" } + ] } ] } diff --git a/src/v2/utils.ts b/src/v2/utils.ts index b9fc14c..11127cf 100644 --- a/src/v2/utils.ts +++ b/src/v2/utils.ts @@ -58,18 +58,26 @@ export const createControl = ( break; case 'CHOICE_LIST': - if (uiField.inputType === 'DROPDOWN') { - control.options!.format = 'dropdown'; - if (uiField.placeholder) { - control.options!.placeholder = uiField.placeholder; + // Handle multiple choice (array type) first + if (property.type === 'array') { + control.options!.multi = true; + // For arrays, use appropriate multi-select format + if (uiField.inputType === 'DROPDOWN') { + control.options!.format = 'dropdown'; + } else if (uiField.inputType === 'LIST') { + control.options!.format = 'checkbox'; + } + } else { + // Single select + if (uiField.inputType === 'DROPDOWN') { + control.options!.format = 'dropdown'; + } else if (uiField.inputType === 'LIST') { + control.options!.format = 'radio'; } - } else if (uiField.inputType === 'LIST') { - control.options!.format = 'radio'; } - // Handle multiple choice (array type) - if (property.type === 'array') { - control.options!.multi = true; + if (uiField.placeholder) { + control.options!.placeholder = uiField.placeholder; } break;