UNPKG

graphql-mandatory-validator

Version:

A GraphQL schema validator using AST-only parsing for mandatory fields with default values, array validation, and composite type validation

335 lines (267 loc) 8.53 kB
# graphql-mandatory-validator A Node.js package for validating GraphQL schema files to ensure mandatory fields have appropriate default values. This tool helps maintain schema consistency across multiple repositories and prevents breaking changes. ## Features - Validates mandatory GraphQL fields have default values - 🏗️ Validates mandatory composite types have at least one mandatory field - 🎯 **NEW**: Validates mandatory enum fields have default values - 🔧 Configurable scalar type defaults - 📁 Works with standard `src/type-defs` directory structure - 🚀 Can validate staged files (git pre-commit) or entire projects - 🎨 Colored terminal output with detailed error messages - 📦 Available as both CLI tool and programmatic API - 🔄 Supports multiple repositories with consistent structure ## Installation ### Global Installation (CLI usage) ```bash npm install -g graphql-mandatory-validator ``` ### Local Installation (Project dependency) ```bash npm install --save-dev graphql-mandatory-validator ``` ## CLI Usage ### Basic Usage ```bash # Validate staged .graphql files (for pre-commit hooks) graphql-validator # Validate all .graphql files in the project graphql-validator --mode all ``` ### Advanced Options ```bash # Use custom base directory graphql-validator --base-dir schema/definitions # Validate specific project root graphql-validator --mode all --project-root /path/to/project # Disable colored output graphql-validator --no-color # Don't exit process on validation failure graphql-validator --no-exit ``` ### Help ```bash graphql-validator --help ``` ## Programmatic API ### Basic Usage ```typescript import { GraphQLValidator } from 'graphql-mandatory-validator'; const validator = new GraphQLValidator(); // Validate staged files const result = await validator.validateStagedFiles(); // Validate entire project const result = await validator.validateProject(); console.log(`Validation ${result.success ? 'passed' : 'failed'}`); console.log(`Files checked: ${result.filesChecked}`); console.log(`Errors found: ${result.errors.length}`); ``` ### Advanced Configuration ```typescript import { GraphQLValidator } from 'graphql-mandatory-validator'; const validator = new GraphQLValidator({ baseDir: 'custom/graphql/path', colorOutput: false, exitOnError: false, scalarDefaults: { String: '@defaultValue(value: "")', Int: '@defaultValue(value: 0)', Float: '@defaultValue(value: 0.0)', Boolean: '@defaultValue(value: false)', ID: '@defaultValue(value: "")', // Add custom scalar types DateTime: '@defaultValue(value: "1970-01-01T00:00:00Z")', } }); const result = await validator.run('all', '/path/to/project'); ``` ## Validation Rules ### 1. Scalar Type Default Values The validator enforces the following default values for GraphQL scalar types: | GraphQL Type | Required Default Value | |--------------|------------------------| | `String!` | `@defaultValue(value: "")` | | `Int!` | `@defaultValue(value: 0)` | | `Float!` | `@defaultValue(value: 0.0)` | | `Boolean!` | `@defaultValue(value: false)` | | `ID!` | `@defaultValue(value: "")` | ### 2. Composite Type Validation The validator ensures that if a composite type is used as a mandatory field, the composite type itself must contain at least one mandatory field. **Why this rule?** If a composite type has no mandatory fields, making it mandatory doesn't provide any meaningful constraint - it could be an empty object and still satisfy the schema. ### 3. Enum Type Validation **NEW**: The validator ensures that mandatory enum fields have appropriate default values. **Default Value Format**: `@defaultValue(value: "ENUM_VALUE")` **Recommended Values**: - Use `"UNKNOWN"` if available in the enum - Otherwise, use any valid enum value **Why this rule?** Mandatory enum fields without defaults can break existing queries when new enum types are introduced. ## Example GraphQL Schema ### Scalar Type Validation #### ❌ Invalid (will fail validation) ```graphql type User { id: ID! name: String! age: Int! isActive: Boolean! } ``` #### ✅ Valid (passes validation) ```graphql type User { id: ID! @defaultValue(value: "") name: String! @defaultValue(value: "") age: Int! @defaultValue(value: 0) isActive: Boolean! @defaultValue(value: false) } ``` ### Composite Type Validation #### ❌ Invalid (will fail validation) ```graphql type User { id: ID! @defaultValue(value: "") profile: Profile! # ERROR: Profile has no mandatory fields } type Profile { bio: String # Optional field avatar: String # Optional field website: String # Optional field } ``` **Error**: `Mandatory composite field "profile" of type "Profile!" references a type with no mandatory fields. Type "Profile" should have at least one mandatory field.` #### ✅ Valid (passes validation) ```graphql # Option 1: Add mandatory field to composite type type User { id: ID! @defaultValue(value: "") profile: Profile! } type Profile { userId: ID! @defaultValue(value: "") # Now has mandatory field bio: String avatar: String website: String } # Option 2: Make composite field optional type User { id: ID! @defaultValue(value: "") profile: Profile # Made optional (no !) } type Profile { bio: String avatar: String website: String } ``` ### Enum Type Validation #### ❌ Invalid (will fail validation) ```graphql type Order { id: ID! @defaultValue(value: "") status: OrderStatus! # ERROR: Missing default value priority: Priority! # ERROR: Missing default value } ``` **Error**: `Mandatory field "status" of type "OrderStatus!" must have a default value. For enum types, use @defaultValue(value: "UNKNOWN") or @defaultValue(value: "<enum_value>") with one of the enum values.` #### ✅ Valid (passes validation) ```graphql # Option 1: Using UNKNOWN (recommended) type Order { id: ID! @defaultValue(value: "") status: OrderStatus! @defaultValue(value: "UNKNOWN") priority: Priority! @defaultValue(value: "UNKNOWN") } # Option 2: Using specific enum values type Task { id: ID! @defaultValue(value: "") status: TaskStatus! @defaultValue(value: "PENDING") priority: Priority! @defaultValue(value: "MEDIUM") } # Option 3: Mixed approach type Product { id: ID! @defaultValue(value: "") category: Category! @defaultValue(value: "UNKNOWN") # Has UNKNOWN status: ProductStatus! @defaultValue(value: "DRAFT") # No UNKNOWN, use first value } ``` ## Git Pre-commit Hook Integration ### Using Husky 1. Install husky: ```bash npm install --save-dev husky npx husky install ``` 2. Add pre-commit hook: ```bash npx husky add .husky/pre-commit "graphql-validator" ``` ### Using pre-commit (Python) Add to your `.pre-commit-config.yaml`: ```yaml repos: - repo: local hooks: - id: graphql-validator name: GraphQL Schema Validator entry: graphql-validator language: node files: \\.graphql$ pass_filenames: false ``` ## Package.json Scripts Add to your `package.json`: ```json { "scripts": { "validate-graphql": "graphql-validator --mode all", "validate-graphql:staged": "graphql-validator" } } ``` ## Configuration Options ### Constructor Options ```typescript interface ValidationOptions { baseDir?: string; // Default: 'src/type-defs' scalarDefaults?: Record<string, string>; colorOutput?: boolean; // Default: true exitOnError?: boolean; // Default: true } ``` ### ValidationResult ```typescript interface ValidationResult { success: boolean; errors: ValidationError[]; filesChecked: number; } interface ValidationError { file: string; line: number; fieldName: string; fieldType: string; expectedDefault: string; message: string; } ``` ## Repository Structure Requirements This package assumes your GraphQL files are located in: ``` your-project/ ├── src/ └── type-defs/ ├── schema1.graphql ├── schema2.graphql └── ... └── package.json ``` You can customize the base directory using the `baseDir` option. ## License MIT ## Contributing 1. Fork the repository 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 4. Push to the branch (`git push origin feature/amazing-feature`) 5. Open a Pull Request ## Support For issues and questions, please open an issue on the GitHub repository.