UNPKG

defarm-sdk

Version:

DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain

252 lines (226 loc) 7.51 kB
import type { DeFarmConfig, Asset, ValidationResult, ValidationError } from '../types'; /** * Service for validating assets according to DeFarm rules */ export class ValidationService { private config: DeFarmConfig; constructor(config: DeFarmConfig) { this.config = config; } async validateAsset(asset: Asset): Promise<ValidationResult> { const errors: ValidationError[] = []; const warnings: any[] = []; // Core validations this.validateRequiredFields(asset, errors); this.validateDataTypes(asset, errors); this.validateBusinessRules(asset, errors, warnings); // Security validations if enabled if (this.config.security.dataClassification) { this.validateDataClassification(asset, errors); } return { valid: errors.length === 0, errors: errors.length > 0 ? errors : undefined, warnings: warnings.length > 0 ? warnings : undefined }; } private validateRequiredFields(asset: Asset, errors: ValidationError[]): void { // Name validation if (!asset.name || asset.name.trim().length === 0) { errors.push({ field: 'name', message: 'Asset name is required', code: 'REQUIRED_FIELD' }); } // Category validation const validCategories = ['livestock', 'crops', 'aquaculture', 'forestry']; if (!asset.category || !validCategories.includes(asset.category)) { errors.push({ field: 'category', message: 'Valid category is required', code: 'INVALID_CATEGORY' }); } // Ownership validation if (!asset.ownership) { errors.push({ field: 'ownership', message: 'Ownership information is required', code: 'REQUIRED_FIELD' }); } else { if (!asset.ownership.ownerId) { errors.push({ field: 'ownership.ownerId', message: 'Owner ID is required', code: 'REQUIRED_FIELD' }); } if (!asset.ownership.ownerName) { errors.push({ field: 'ownership.ownerName', message: 'Owner name is required', code: 'REQUIRED_FIELD' }); } } // Metadata validation if (!asset.metadata) { errors.push({ field: 'metadata', message: 'Metadata is required', code: 'REQUIRED_FIELD' }); } else { if (!asset.metadata.source) { errors.push({ field: 'metadata.source', message: 'Source is required', code: 'REQUIRED_FIELD' }); } if (asset.metadata.fromProducer === undefined) { errors.push({ field: 'metadata.fromProducer', message: 'From producer flag is required', code: 'REQUIRED_FIELD' }); } } } private validateDataTypes(asset: Asset, errors: ValidationError[]): void { // Validate metadata types if (asset.metadata) { const validSources = ['producer', 'authority', 'enterprise', 'association']; if (asset.metadata.source && !validSources.includes(asset.metadata.source)) { errors.push({ field: 'metadata.source', message: 'Invalid source type', code: 'INVALID_VALUE' }); } const validVisibility = ['public', 'private', 'partners']; if (asset.metadata.visibility && !validVisibility.includes(asset.metadata.visibility)) { errors.push({ field: 'metadata.visibility', message: 'Invalid visibility type', code: 'INVALID_VALUE' }); } } // Validate location coordinates if present if (asset.ownership?.location?.coordinates) { const [lng, lat] = asset.ownership.location.coordinates; if (typeof lng !== 'number' || typeof lat !== 'number') { errors.push({ field: 'ownership.location.coordinates', message: 'Coordinates must be numbers', code: 'INVALID_TYPE' }); } else if (lng < -180 || lng > 180 || lat < -90 || lat > 90) { errors.push({ field: 'ownership.location.coordinates', message: 'Invalid coordinate values', code: 'INVALID_VALUE' }); } } } private validateBusinessRules(asset: Asset, errors: ValidationError[], warnings: any[]): void { // Validate source and fromProducer consistency if (asset.metadata?.source === 'producer' && !asset.metadata.fromProducer) { warnings.push({ field: 'metadata.fromProducer', message: 'Source is producer but fromProducer is false', suggestion: 'Set fromProducer to true for producer-sourced assets' }); } // Validate location requirements for certain categories if (asset.category === 'livestock' && !asset.ownership?.location?.country) { warnings.push({ field: 'ownership.location.country', message: 'Country is recommended for livestock traceability', suggestion: 'Add country information for better traceability' }); } // Validate DFID format if present if (asset.dfid) { const dfidPattern = /^DF[A-Z]{2}[A-Z0-9]{10,20}$/; if (!dfidPattern.test(asset.dfid)) { errors.push({ field: 'dfid', message: 'Invalid DFID format', code: 'INVALID_FORMAT' }); } } } private validateDataClassification(asset: Asset, errors: ValidationError[]): void { // Ensure sensitive data is properly marked if (asset.metadata?.visibility === 'public') { // Check for potentially sensitive fields in public data const sensitiveFields = [ 'personal_id', 'tax_id', 'bank_account', 'phone_number', 'email' ]; for (const field of sensitiveFields) { if (asset.data?.[field]) { errors.push({ field: `data.${field}`, message: 'Sensitive data cannot be public', code: 'DATA_CLASSIFICATION_ERROR' }); } } } // Validate encryption requirements if (this.config.security.encryptionEnabled && asset.metadata?.visibility === 'private') { if (!asset.data?._encrypted) { errors.push({ field: 'data', message: 'Private data must be encrypted', code: 'ENCRYPTION_REQUIRED' }); } } } /** * Validate a batch of assets */ async validateBatch(assets: Asset[]): Promise<Map<number, ValidationResult>> { const results = new Map<number, ValidationResult>(); await Promise.all( assets.map(async (asset, index) => { const result = await this.validateAsset(asset); results.set(index, result); }) ); return results; } /** * Check if an asset meets minimum quality standards */ isHighQuality(asset: Asset): boolean { // Asset must have certain fields to be considered high quality const hasDetailedLocation = asset.ownership?.location?.country && asset.ownership?.location?.state && asset.ownership?.location?.coordinates; const hasIdentifiers = asset.dfid || asset.data?.ear_tag || asset.data?.rfid || asset.data?.batch_number; const hasCompleteMetadata = asset.metadata?.source && asset.metadata?.fromProducer !== undefined && asset.metadata?.visibility && asset.metadata?.created && asset.metadata?.version; return !!(hasDetailedLocation && hasIdentifiers && hasCompleteMetadata); } }