Skip to content

Conversation

@marcodejongh
Copy link
Owner

Implements a lightweight code generator that parses the GraphQL schema and
generates C++ header files with ArduinoJson-compatible structs for the
ESP32 firmware.

Key components:

  • generate-graphql-types.mjs: Node.js script that parses schema.ts and
    generates graphql_types.h with structs, operations, and JSON helpers
  • prebuild.py: PlatformIO pre-build hook that automatically regenerates
    types when the schema changes (hash-based change detection)
  • graphql-types library: New PlatformIO library containing generated types

Generated types include:

  • LedCommand, LedCommandInput (with uint8_t for RGB values)
  • LedUpdate, ControllerPing, ClimbMatchResult
  • DeviceLogEntry, SendDeviceLogsResponse
  • GraphQL operation string constants
  • JSON parsing/serialization helper functions

This provides type safety between the GraphQL backend and ESP32 firmware
without heavy dependencies like cppgraphqlgen.

https://claude.ai/code/session_01CkAwoY8k1wbNeCMJ14Xfzv

Implements a lightweight code generator that parses the GraphQL schema and
generates C++ header files with ArduinoJson-compatible structs for the
ESP32 firmware.

Key components:
- generate-graphql-types.mjs: Node.js script that parses schema.ts and
  generates graphql_types.h with structs, operations, and JSON helpers
- prebuild.py: PlatformIO pre-build hook that automatically regenerates
  types when the schema changes (hash-based change detection)
- graphql-types library: New PlatformIO library containing generated types

Generated types include:
- LedCommand, LedCommandInput (with uint8_t for RGB values)
- LedUpdate, ControllerPing, ClimbMatchResult
- DeviceLogEntry, SendDeviceLogsResponse
- GraphQL operation string constants
- JSON parsing/serialization helper functions

This provides type safety between the GraphQL backend and ESP32 firmware
without heavy dependencies like cppgraphqlgen.

https://claude.ai/code/session_01CkAwoY8k1wbNeCMJ14Xfzv
@vercel
Copy link

vercel bot commented Feb 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
boardsesh Building Building Preview, Comment Feb 3, 2026 11:29pm

Request Review

@claude
Copy link

claude bot commented Feb 3, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. Memory leak in parseLedUpdate (graphql_types.h:177-192): The function allocates memory with new LedCommand[update.commandsCount] but the comment says "caller must free" - this is error-prone. Consider using a stack-allocated fixed-size array or providing a cleanup helper function.

  2. String lifetime issue (graphql_types.h:189-190): update.climbUuid and update.climbName are assigned from obj["climbUuid"] which returns a pointer to ArduinoJson's internal buffer. These pointers become invalid when the JsonDocument is destroyed, which could cause use-after-free bugs.

  3. Type mismatch for position field (graphql_types.h:39,51): LedCommand.position is defined as int32_t in the generated C++ but the schema defines it as Int!. While these are compatible, the original led_controller.h used plain int - verify this won't cause issues on ESP32 where int is typically 32-bit anyway.

  4. Hardcoded operations (graphql_types.h:107-134): The GraphQL operations are hardcoded strings in the generated file rather than being extracted from the schema. If the backend operations change, these won't automatically update - the generator only parses types, not operations.

  5. Missing null check in serializeLedCommandInput (graphql_types.h:211-219): The function checks if (cmd.role >= 0) but role is int32_t which could legitimately be negative. The schema shows role: Int (nullable), but the C++ struct doesn't distinguish null from zero/negative values.

Adds comprehensive test suites for the code generation system:

Node.js tests (21 tests):
- Schema parsing for types, inputs, unions, arrays
- GraphQL to C++ type mapping
- C++ struct generation with field overrides
- Integration tests with real schema files
- Edge cases (empty schema, missing types)

Python tests (9 tests):
- Hash calculation for file change detection
- Combined hash for multiple files
- Hash storage and retrieval
- Integration with real schema files

Run all tests with: npm run controller:codegen:test

https://claude.ai/code/session_01CkAwoY8k1wbNeCMJ14Xfzv
@claude
Copy link

claude bot commented Feb 3, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. Memory allocation failure not handled (embedded/libs/graphql-types/src/graphql_types.h:235) - new LedCommand[update.commandsCount] can fail on ESP32's limited RAM. Consider checking for nullptr after allocation.

  2. Nullable angle could be masked (embedded/libs/graphql-types/src/graphql_types.h:243) - update.angle = obj["angle"] | 0 defaults nullable Int to 0, which is a valid angle value. If distinguishing "no angle" from "angle=0" matters, this could cause issues.

  3. Test code duplication (embedded/scripts/generate-graphql-types.test.mjs:56-105) - Parser logic is duplicated in tests rather than imported from the source module. Consider exporting testable functions.

Fixes several issues identified in code review:

1. Memory leak prevention: Added freeLedUpdate() helper function to
   properly deallocate the commands array allocated by parseLedUpdate()

2. Dangling pointer documentation: Added clear documentation about
   string pointer lifetime - const char* fields point into JsonDocument
   memory and become invalid when the document is destroyed

3. Removed timestamp from generated header to avoid noisy diffs on
   every regeneration - the file can now be committed without changes
   unless the schema actually changed

4. Fixed sentinel value comparison: Added ROLE_NOT_SET constant (-1)
   and updated serializeLedCommandInput() to use explicit sentinel
   comparison instead of >= 0

5. Removed leftover console.log from test file

6. Added new tests verifying:
   - freeLedUpdate helper is generated
   - ROLE_NOT_SET constant and usage
   - Pointer lifetime documentation
   - No timestamp in generated output

https://claude.ai/code/session_01CkAwoY8k1wbNeCMJ14Xfzv
Addresses additional review feedback:

1. Fix native test build failure:
   - Added LEDCOMMAND_DEFINED include guard to both led_controller.h
     and generated graphql_types.h
   - This allows native tests to work without Arduino.h dependency
   - led_controller.h defines LedCommand for native tests
   - graphql_types.h skips definition if already defined

2. Handle memory allocation failure:
   - Changed new[] to new (std::nothrow) in parseLedUpdate()
   - Returns false if allocation fails instead of crashing
   - Added #include <new> for std::nothrow

3. Document nullable angle behavior:
   - Added note that angle defaults to 0 if not present
   - Since 0 is valid, callers must use containsKey() if they
     need to distinguish "no angle" from "angle=0"

4. Added tests verifying:
   - LEDCOMMAND_DEFINED include guard is generated
   - std::nothrow is used for allocation
   - Allocation failure handling
   - Angle nullable documentation

https://claude.ai/code/session_01CkAwoY8k1wbNeCMJ14Xfzv
@claude
Copy link

claude bot commented Feb 3, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. MD5 for integrity checking - embedded/scripts/prebuild.py:15,22: Uses MD5 for hash-based change detection. While not a security vulnerability here (it's just for caching), consider using SHA256 for consistency with modern practices.

  2. Generated file committed to repo - embedded/libs/graphql-types/src/graphql_types.h is checked in but marked as auto-generated. This can lead to merge conflicts if multiple PRs modify the schema. Consider adding it to .gitignore and generating during CI/build only.

  3. Test duplication - embedded/scripts/generate-graphql-types.test.mjs:101-127 re-implements functions from the main module instead of importing them. This means tests could pass even if the actual implementation differs. Consider refactoring the main module to export testable functions.

  4. Angle field ambiguity undocumented in schema - The generated C++ code has good documentation about angle defaulting to 0, but the original schema at packages/shared-schema/src/schema.ts:1437 shows angle: Int (nullable) without documenting that 0 is a valid value. This semantic ambiguity exists at the source.

Notes

  • Tests are comprehensive and cover parsing, type mapping, struct generation, and edge cases
  • Both JS and Python tests are included which is good
  • The prebuild hook gracefully handles missing Node.js

1. Switch from MD5 to SHA256 for hash-based change detection
   - More consistent with modern practices
   - Updated both prebuild.py and test_prebuild.py
   - Updated test assertions for 64-char SHA256 hex length

2. Add generated files to .gitignore
   - graphql_types.h is now generated during build only
   - .schema_hash cache file also ignored
   - Prevents merge conflicts when multiple PRs modify schema

3. Document angle nullable ambiguity in GraphQL schema
   - Added doc comment to LedUpdate.angle explaining that
     null means "no angle" vs 0 being a valid angle value
   - Source documentation prevents ambiguity at the source

https://claude.ai/code/session_01CkAwoY8k1wbNeCMJ14Xfzv
@claude
Copy link

claude bot commented Feb 3, 2026

Claude Review

Ready to merge - Well-structured code generation implementation with comprehensive tests. Minor issues noted below.

Issues

  1. Test code duplication (embedded/scripts/generate-graphql-types.test.mjs:627-749): The test file duplicates core functions from the source instead of importing them. Consider refactoring the main script to export functions for testability, avoiding drift between source and test implementations.

  2. Missing error handling in parseLedUpdate (generate-graphql-types.mjs:491-494): The generated parseLedCommand call in the loop doesn't check return value - if parsing fails, the command array may contain uninitialized data.

  3. Potential memory leak pattern (generated code): The inline parseLedUpdate function allocates memory via new, but if parsing of later fields fails or the caller forgets freeLedUpdate, memory leaks occur. Consider documenting the ownership contract more prominently or using RAII patterns.

  4. Schema change in PR (packages/shared-schema/src/schema.ts:1430-1435): Added angle documentation to LedUpdate, but this is just a doc comment addition - no breaking changes.

  5. Hash collision edge case (prebuild.py:148): The combined hash concatenates two SHA256 hashes as strings before hashing again. While functionally correct, this is a minor inefficiency - could hash the file contents directly in sequence.

- Check return value of parseLedCommand in generated parseLedUpdate loop
- Free allocated memory and return false if command parsing fails
- Simplify hash computation to hash file contents directly in one pass
- Remove obsolete get_file_hash tests from test_prebuild.py

https://claude.ai/code/session_01CkAwoY8k1wbNeCMJ14Xfzv
@claude
Copy link

claude bot commented Feb 3, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. prebuild.py:123 - Missing newline at end of file.

  2. generate-graphql-types.mjs:81 - The regex /\{([^}]+)\}/ doesn't handle nested braces. If a type body ever contains a nested object (e.g., a directive with @deprecated(reason: "...")), parsing could break. Not a current issue but worth noting for future schema changes.

  3. prebuild.py:120-123 - The before_build hook is registered twice (for "buildprog" and for "main.cpp.o"), which could cause duplicate codegen runs during a single build. Consider using a flag to prevent re-execution.

  4. generate-graphql-types.mjs:410 - angle defaults to 0, but the schema comment says null indicates "no angle". The generated struct uses int32_t which can't represent null. Consider using a sentinel value (like ROLE_NOT_SET) or documenting this limitation more prominently in the struct definition.

Test Coverage

Tests are comprehensive and cover schema parsing, type mapping, struct generation, and edge cases. The integration tests verify the generated output file structure and content.

- Add ANGLE_NOT_SET sentinel (-32768) for nullable angle field
- Prevent duplicate codegen runs during single build with flag
- Add newline at end of prebuild.py
- Document regex limitation for nested braces
- Update test for new angle sentinel behavior

https://claude.ai/code/session_01CkAwoY8k1wbNeCMJ14Xfzv
@claude
Copy link

claude bot commented Feb 3, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. Test code duplication - generate-graphql-types.test.mjs:636-761 duplicates the core parsing functions from the source file rather than importing them. This means test coverage doesn't actually verify the real implementation. Consider refactoring the source to export testable functions.

  2. Global mutable state - prebuild.py:148 uses a global _codegen_ran flag for deduplication. This works but could cause issues if the script is reloaded during a build session.

  3. Schema parser limitation - generate-graphql-types.mjs:178 comment notes the regex doesn't handle nested braces. While this is documented, if the schema adds directives with arguments (e.g., @deprecated(reason: "...")), parsing will break silently.

  4. Missing test for parse failure cleanup - The parseLedUpdate function cleans up on parse failure, but there's no test verifying the cleanup behavior when parseLedCommand fails mid-array.

  5. Hardcoded constant duplication - ROLE_NOT_SET and ANGLE_NOT_SET are defined in both the JS generator (lines 161-162) and the generated header. If the values diverge, behavior becomes inconsistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants