All files / src/validators endpointValidator.js

54.54% Statements 36/66
40.38% Branches 21/52
60% Functions 3/5
53.12% Lines 34/64

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165              2x             2x   2x             2x           2x 2x 2x     2x 53x 53x     53x         53x           53x         53x 53x         53x 53x     53x     53x 53x           53x             53x 53x       53x           2x               2x 53x 53x         2x       2x       10x   2x                 2x                                                                         3x
class EndpointValidator {
  /**
   * Validates service endpoints
   * @param {Array} endpoints - Array of endpoint definitions
   * @returns {Promise<object>} Validation result
   */
  async validate(endpoints) {
    const result = {
      valid: true,
      errors: [],
      warnings: [],
      info: {}
    };
 
    try {
      // Check if endpoints is an array
      Iif (!Array.isArray(endpoints)) {
        result.valid = false;
        result.errors.push('Endpoints must be an array');
        return result;
      }
 
      // Check for at least one endpoint
      Iif (endpoints.length === 0) {
        result.valid = false;
        result.errors.push('Service must define at least one endpoint');
        return result;
      }
 
      result.info.count = endpoints.length;
      const paths = new Set();
      const validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
 
      // Validate each endpoint
      for (let i = 0; i < endpoints.length; i++) {
        const endpoint = endpoints[i];
        const endpointPrefix = `Endpoint[${i}]`;
 
        // Validate required fields
        Iif (!endpoint.path) {
          result.errors.push(`${endpointPrefix}: Missing required "path" field`);
          continue;
        }
 
        Iif (!endpoint.method) {
          result.errors.push(`${endpointPrefix} ${endpoint.path}: Missing required "method" field`);
          continue;
        }
 
        // Validate path format
        Iif (!endpoint.path.startsWith('/')) {
          result.errors.push(`${endpointPrefix}: Path "${endpoint.path}" must start with /`);
        }
 
        // Validate method
        const method = endpoint.method.toUpperCase();
        Iif (!validMethods.includes(method)) {
          result.errors.push(`${endpointPrefix} ${endpoint.path}: Invalid method "${endpoint.method}"`);
        }
 
        // Check for duplicates
        const key = `${method} ${endpoint.path}`;
        Iif (paths.has(key)) {
          result.errors.push(`${endpointPrefix}: Duplicate endpoint definition: ${key}`);
        }
        paths.add(key);
 
        // Validate handler
        Eif (endpoint.handler) {
          Iif (typeof endpoint.handler !== 'string' && typeof endpoint.handler !== 'function') {
            result.errors.push(`${endpointPrefix} ${endpoint.path}: Handler must be a string or function`);
          }
        }
 
        // Validate middleware (if present)
        Iif (endpoint.middleware) {
          if (!Array.isArray(endpoint.middleware)) {
            result.warnings.push(`${endpointPrefix} ${endpoint.path}: Middleware should be an array`);
          }
        }
 
        // Check for authentication
        Eif (endpoint.auth === undefined) {
          result.warnings.push(`${endpointPrefix} ${endpoint.path}: Consider explicitly setting auth requirement`);
        }
 
        // Validate rate limiting
        Iif (endpoint.rateLimit && typeof endpoint.rateLimit !== 'object') {
          result.warnings.push(`${endpointPrefix} ${endpoint.path}: Rate limit should be an object`);
        }
      }
 
      // Additional checks
      const hasMethods = {
        GET: false,
        POST: false,
        PUT: false,
        PATCH: false,
        DELETE: false
      };
 
      endpoints.forEach(ep => {
        Eif (ep.method) {
          hasMethods[ep.method.toUpperCase()] = true;
        }
      });
 
      // Check for REST compliance
      Iif (hasMethods.POST && !hasMethods.GET) {
        result.warnings.push('REST API should provide GET endpoints for resources that can be created');
      }
 
      Iif (hasMethods.DELETE && !hasMethods.GET) {
        result.warnings.push('REST API should provide GET endpoints for resources that can be deleted');
      }
 
      result.info.methods = Object.keys(hasMethods).filter(m => hasMethods[m]);
 
      Iif (result.errors.length > 0) {
        result.valid = false;
      }
 
    } catch (error) {
      result.valid = false;
      result.errors.push(`Endpoint validation error: ${error.message}`);
    }
 
    return result;
  }
 
  /**
   * Generate endpoint documentation
   * @param {Array} endpoints - Array of endpoint definitions
   * @returns {object} Generated documentation
   */
  generateDocumentation(endpoints) {
    const docs = {
      endpoints: [],
      summary: {
        total: endpoints.length,
        byMethod: {}
      }
    };
 
    const methodCounts = {};
 
    endpoints.forEach(endpoint => {
      const method = endpoint.method?.toUpperCase() || 'UNKNOWN';
      methodCounts[method] = (methodCounts[method] || 0) + 1;
 
      docs.endpoints.push({
        method: method,
        path: endpoint.path,
        description: endpoint.description || 'No description provided',
        auth: endpoint.auth !== undefined ? endpoint.auth : 'Not specified',
        handler: endpoint.handler || 'Not specified'
      });
    });
 
    docs.summary.byMethod = methodCounts;
    return docs;
  }
}
 
module.exports = EndpointValidator;