Skip to content

feat: planned workouts TypeSpec and OpenAPI spec#5

Open
terra-alex wants to merge 2 commits into
masterfrom
alex/planned-workouts-spec
Open

feat: planned workouts TypeSpec and OpenAPI spec#5
terra-alex wants to merge 2 commits into
masterfrom
alex/planned-workouts-spec

Conversation

@terra-alex
Copy link
Copy Markdown
Contributor

@terra-alex terra-alex commented Mar 4, 2026

Summary

  • Add TypeSpec definition for the Planned Workouts API (PlannedWorkouts.tsp)
  • Add generated OpenAPI spec (workouts.yaml)
  • Add tspconfig for the data models package
  • Update CI workflow and package dependencies for TypeSpec compilation

Endpoints covered

  • POST /workouts — Create workout template
  • POST /workouts/{id}/plan?user_id=X — Schedule workout to user
  • GET /plannedWorkouts?user_id=X — List planned workouts
  • GET /plannedWorkouts/{id} — Get planned workout
  • PATCH /plannedWorkouts/{id}?user_id=X — Update planned date
  • DELETE /plannedWorkouts/{id}?user_id=X — Remove planned workout

TODO (follow-up)

  • Add missing template endpoints: GET /workouts, GET /workouts/{id}, DELETE /workouts/{id}
  • Add workout_id to template GET response model

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a comprehensive Workouts API spec with endpoints for workout templates, planning workouts, and managing planned workouts (list/get/update/delete).
  • Chores

    • Updated CI to generate, validate, and publish the OpenAPI spec as a release artifact and asset; added a gated release creation step.
    • Bumped Node.js in the pipeline and updated TypeSpec-related dependencies and config to emit the OpenAPI spec.

Add TypeSpec definition for the Planned Workouts API including:
- Workout template CRUD (create, plan/schedule)
- Planned workout management (list, get, update, delete)
- Full enum definitions for sport types, intensity types, targets
- Coercion warning model for provider compatibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

Adds a TypeSpec-based Terra Workouts API specification and OpenAPI emitter config, updates TypeSpec dependencies, and extends CI/CD to generate, validate, and publish workouts.yaml as a release artifact; introduces multiple new enums, models, and operations for workouts and planned workouts.

Changes

Cohort / File(s) Summary
CI/CD Pipeline
.github/workflows/release.yml
Updated Node.js from v18 to v22; added step to emit workouts.yaml via TypeSpec, validate workouts.yaml in the OpenAPI bundle validation step, include workouts.yaml in upload-artifact and release asset steps, and added a release creation step gated to tag pushes.
TypeSpec Dependencies
data_models/package.json
Updated @typespec/json-schema to latest; added @typespec/http, @typespec/rest, and @typespec/openapi3 (all latest).
TypeSpec API Specification
data_models/specs/data_models/PlannedWorkouts.tsp
New comprehensive TypeSpec API: Terra.Workouts namespace, TerraHeaders, numerous enums (SportType, EnvironmentType, CompletionConditionType, IntensityType, TargetType, WeightUnit, SwimStroke, DrillType, SwimEquipment), many data models (CompletionCondition, IntensityTarget, StrengthData, SwimmingStepData, WorkoutStep, StepBlock, WorkoutTemplate, CoercionWarning, PlannedWorkout, etc.), and operations for creating/listing/updating/deleting templates and planned workouts.
TypeSpec Config
data_models/specs/data_models/tspconfig.yaml
New TypeSpec emitter config targeting @typespec/openapi3, outputting workouts.yaml (OpenAPI 3.1.0).
Generated OpenAPI Spec
workouts.yaml
New OpenAPI 3.1.0 spec exposing /workouts and /plannedWorkouts paths, security schemes, component schemas (PlannedWorkout, WorkoutTemplate, IntensityTarget, CompletionCondition, enums, etc.), request/response definitions, examples, and production server URL.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Dev as Developer
  participant GH as GitHub Actions
  participant TS as TypeSpec Emitter
  participant Validator as OpenAPI Validator
  participant Storage as Release Artifacts

  Dev->>GH: push tag / push to main
  GH->>TS: run TypeScript + TypeSpec emit -> produce workouts.yaml
  TS-->>GH: workouts.yaml
  GH->>Validator: validate workouts.yaml in bundle
  Validator-->>GH: validation result
  GH->>Storage: upload workouts.yaml + other artifacts
  GH->>Storage: create release (create_release) with assets
  Storage-->>GH: release published
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through TypeSpec, nibbling each line,
Spun YAML from models, enums by design,
CI baked the bundle, validated with care,
Released tiny artifacts floating in air,
A joyful carrot—workouts now shine! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: planned workouts TypeSpec and OpenAPI spec' directly and concisely describes the main change: adding TypeSpec definitions and OpenAPI specifications for the Planned Workouts API.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch alex/planned-workouts-spec

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a foundational shift in how the Planned Workouts API is defined and documented, moving to a TypeSpec-driven approach. This change establishes a single source of truth for the API contract, improving consistency, maintainability, and the developer experience for consumers. The automated generation of the OpenAPI specification from TypeSpec ensures that API documentation remains accurate with code changes.

Highlights

  • TypeSpec Definition for Planned Workouts API: A new TypeSpec definition file (PlannedWorkouts.tsp) has been added, comprehensively defining the Planned Workouts API, including its data models, enums, and operations.
  • OpenAPI Specification Generation: An OpenAPI 3.1.0 specification (workouts.yaml) is now automatically generated from the TypeSpec definition, ensuring up-to-date and consistent API documentation.
  • TypeSpec Integration and Dependency Updates: The project's dependencies (package.json, package-lock.json) have been updated to include necessary TypeSpec packages (@typespec/http, @typespec/rest, @typespec/openapi3) and numerous other library updates to support the new TypeSpec compilation workflow.
  • TypeSpec Configuration: A tspconfig.yaml file has been introduced to configure the TypeSpec compiler, specifically for generating the OpenAPI specification.
  • API Endpoints Covered: The new specification covers key endpoints for workout templates (create) and planned workouts (schedule, list, get, update, delete), providing a structured definition for these operations.
Changelog
  • data_models/package-lock.json
    • Updated core TypeSpec dependencies to 'latest' versions, including '@typespec/json-schema', and added new TypeSpec packages like '@typespec/http', '@typespec/openapi3', and '@typespec/rest'.
    • Performed a broad update of various other development and runtime dependencies, including '@babel', '@InQuirer' suite, '@scalar' packages, 'globby', 'iconv-lite', 'tar', 'yaml', 'yargs', 'string-width', 'strip-ansi', 'ansi-regex', 'mute-stream', 'unicorn-magic', 'chardet', 'emoji-regex', 'leven', and 'zod'.
    • Removed several outdated or replaced dependencies such as 'external-editor', 'os-tmpdir', 'type-fest', 'require-directory', and 'is-fullwidth-code-point'.
  • data_models/package.json
    • Updated '@typespec/json-schema' to 'latest'.
    • Added '@typespec/http', '@typespec/rest', and '@typespec/openapi3' as dependencies.
  • data_models/specs/data_models/PlannedWorkouts.tsp
    • Added a new TypeSpec file defining the Planned Workouts API.
    • Included enums for SportType, EnvironmentType, CompletionConditionType, IntensityType, TargetType, WeightUnit, SwimStroke, DrillType, and SwimEquipment.
    • Defined models for TerraHeaders, CompletionCondition, IntensityTarget, StrengthData, SwimmingStepData, WorkoutStep, StepBlock, WorkoutTemplate, CoercionWarning, and various request/response models.
    • Declared API operations for creating workout templates, planning workouts, listing, getting, updating, and deleting planned workouts.
  • data_models/specs/data_models/tspconfig.yaml
    • Added a new TypeSpec configuration file.
    • Configured the '@typespec/openapi3' emitter to output 'workouts.yaml' with OpenAPI version '3.1.0'.
  • workouts.yaml
    • Added a new OpenAPI 3.1.0 specification file.
    • This file is generated from 'PlannedWorkouts.tsp' and details all defined API paths, parameters, request bodies, responses, and schemas for the Planned Workouts API.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/release.yml
Activity
  • The pull request was created to introduce TypeSpec definitions and OpenAPI specifications for the Planned Workouts API.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces TypeSpec definitions and a generated OpenAPI specification for the Planned Workouts API. While the changes are well-structured, a significant security concern exists due to an inconsistency in the authorization scoping of the planned workout endpoints. Specifically, the getPlannedWorkout endpoint is missing the user_id parameter, which poses a high risk of Insecure Direct Object Reference (IDOR). Additionally, some read-only fields in the WorkoutTemplate model are not properly marked, potentially leading to mass assignment issues. Further improvements are needed regarding pinning NPM dependencies, refactoring the WorkoutTemplate model for clearer request/response separation, refining date/date-time type definitions, and renaming an enum member to avoid conflicts. Addressing these points will enhance both the security and overall quality of the API specification.

Comment on lines +490 to +493
op getPlannedWorkout(
...TerraHeaders,
@path planned_workout_id: string
): PlannedWorkout | {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The getPlannedWorkout endpoint (GET /plannedWorkouts/{planned_workout_id}) is missing the user_id query parameter, which is required by all other endpoints that operate on a specific planned workout (e.g., updatePlannedWorkout, deletePlannedWorkout) or list them (listPlannedWorkouts). This inconsistency in the API design creates a high risk of an Insecure Direct Object Reference (IDOR) vulnerability. Without the user_id parameter to scope the request, the backend might not properly verify that the authenticated developer is authorized to access the specific planned workout, potentially allowing unauthorized access to user data if a planned_workout_id is guessed or obtained.

  op getPlannedWorkout(
    ...TerraHeaders,
    @path planned_workout_id: string,
    @query user_id: string
  ): PlannedWorkout | {

Comment thread data_models/package.json
Comment on lines +40 to +43
"@typespec/json-schema": "latest",
"@typespec/http": "latest",
"@typespec/rest": "latest",
"@typespec/openapi3": "latest"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using "latest" for dependencies can lead to unexpected breaking changes and makes builds non-reproducible. It's a best practice to pin dependencies to specific versions. You can use npm install <package-name>@latest to find the latest version and then pin it in package.json. For example, you could use ^ to allow non-breaking updates.

Suggested change
"@typespec/json-schema": "latest",
"@typespec/http": "latest",
"@typespec/rest": "latest",
"@typespec/openapi3": "latest"
"@typespec/json-schema": "^1.8.0",
"@typespec/http": "^1.8.0",
"@typespec/rest": "^0.78.0",
"@typespec/openapi3": "^1.8.0"

Comment on lines +290 to +297
/** Estimated total duration in seconds (read-only, populated on retrieval) */
estimated_duration_seconds?: float32;

/** Estimated total distance in meters (read-only, populated on retrieval) */
estimated_distance_meters?: float32;

/** Estimated total calories (read-only, populated on retrieval) */
estimated_calories?: float32;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The fields estimated_duration_seconds, estimated_distance_meters, and estimated_calories are described as "read-only" but are not marked with the @visibility("read") decorator. This omission can lead to mass assignment vulnerabilities or data inconsistency if these fields are included in CreateWorkoutTemplateRequest. To prevent this, these read-only fields should be explicitly marked with @visibility("read"). Additionally, consider refactoring the WorkoutTemplate model to separate request and response concerns more clearly, potentially using a base model, a request model, and a response model to avoid including read-only fields in creation requests.

  /** Estimated total duration in seconds (read-only, populated on retrieval) */
  @visibility("read")
  estimated_duration_seconds?: float32;

  /** Estimated total distance in meters (read-only, populated on retrieval) */
  @visibility("read")
  estimated_distance_meters?: float32;

  /** Estimated total calories (read-only, populated on retrieval) */
  @visibility("read")
  estimated_calories?: float32;

/** Training Stress Score (exact: value) */
tss: "tss",
/** Intensity Factor (exact: value) */
`if`: "if",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The enum member if is a keyword in many programming languages. While TypeSpec handles this with backticks, it can lead to awkwardly named properties or enums in generated code (e.g., _if, if_). To improve compatibility and clarity, consider renaming it to something more descriptive like intensity_factor.

  intensity_factor: "if",

/** Response from updating a planned workout */
model UpdatePlannedWorkoutResponse {
status: "success";
planned_workout_id?: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In the UpdatePlannedWorkoutResponse model, planned_workout_id is marked as optional. For a successful update operation, it's good practice to return the ID of the resource that was updated to provide clear confirmation to the client. I suggest making this field required in the success response.

  planned_workout_id: string;

Comment on lines +395 to +396
created_at: string;
last_updated_at: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The created_at and last_updated_at fields are defined as string. It's better to use a more specific format for date-time values. TypeSpec provides the utcDateTime type, which represents an RFC 3339 date-time. This improves type safety and API documentation.

  created_at: utcDateTime;
  last_updated_at: utcDateTime;

Comment on lines +473 to +474
@query start_date?: string,
@query end_date?: string
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The start_date and end_date query parameters in listPlannedWorkouts are defined as strings. To ensure they are in the correct YYYY-MM-DD format as indicated in the endpoint description, you should add a @pattern or @format("date") decorator. This makes the API contract explicit and enables validation.

    @query @pattern("^\\d{4}-\\d{2}-\\d{2}$") start_date?: string,
    @query @pattern("^\\d{4}-\\d{2}-\\d{2}$") end_date?: string

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
data_models/package.json (1)

40-43: Pin explicit semver ranges for TypeSpec dependencies instead of latest.

All four @typespec packages (@typespec/json-schema, @typespec/http, @typespec/rest, @typespec/openapi3) currently use latest, which creates floating dependencies. While the lockfile pins concrete versions (1.8.0, 1.8.0, 1.8.0, 0.78.0 respectively), using latest in the manifest makes future regeneration and lockfile refresh unpredictable—upgrades could pull breaking changes unexpectedly. Pin explicit semver ranges (e.g., ^1.8.0) for stability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data_models/package.json` around lines 40 - 43, Replace the floating "latest"
versions in package.json for the TypeSpec deps with explicit semver ranges
matching the current locked versions; update "@typespec/json-schema",
"@typespec/http", and "@typespec/rest" from "latest" to a range like "^1.8.0"
and "@typespec/openapi3" from "latest" to a range like "^0.78.0" so future
installs remain predictable while reflecting the current lockfile versions.
data_models/specs/data_models/PlannedWorkouts.tsp (1)

395-396: Use utcDateTime for timestamp fields to strengthen schema typing.

Replace plain string with the utcDateTime scalar for created_at and last_updated_at. This provides stronger type safety and semantic clarity while encoding as RFC 3339 strings in the OpenAPI output by default.

Suggested change
-  created_at: string;
-  last_updated_at: string;
+  created_at: utcDateTime;
+  last_updated_at: utcDateTime;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data_models/specs/data_models/PlannedWorkouts.tsp` around lines 395 - 396,
The Created/Updated timestamp fields in the PlannedWorkouts model use plain
string types; change the types of created_at and last_updated_at from string to
the utcDateTime scalar to strengthen typing and ensure RFC3339-encoded OpenAPI
output—update the PlannedWorkouts type definition to reference utcDateTime for
those two fields and run any schema/type checks to confirm imports and scalar
declarations (ensure the utcDateTime scalar is available in the schema).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@data_models/specs/data_models/PlannedWorkouts.tsp`:
- Around line 238-239: The PlannedWorkouts request model exposes unbounded
arrays (e.g., the intensity_targets?: IntensityTarget[] property) which need
explicit `@maxItems`(...) bounds; update the PlannedWorkouts model to add a
`@maxItems`(limit) decorator on intensity_targets and the other user-supplied
array properties mentioned in the review (the two other array fields around the
same area) using product-aligned limits, ensure the appropriate validation
decorator/import is present, and run/adjust tests or schema generation to
confirm the maxItems constraint is applied.
- Around line 473-474: The start_date and end_date query parameters in
PlannedWorkouts.tsp currently accept any string; add a `@pattern` decorator
enforcing the YYYY-MM-DD format (e.g. regex ^\d{4}-\d{2}-\d{2}$) to both `@query`
start_date and `@query` end_date declarations so the specs validate dates against
the documented format; update the `@query` lines for start_date and end_date in
PlannedWorkouts.tsp to include the `@pattern` constraint.

---

Nitpick comments:
In `@data_models/package.json`:
- Around line 40-43: Replace the floating "latest" versions in package.json for
the TypeSpec deps with explicit semver ranges matching the current locked
versions; update "@typespec/json-schema", "@typespec/http", and "@typespec/rest"
from "latest" to a range like "^1.8.0" and "@typespec/openapi3" from "latest" to
a range like "^0.78.0" so future installs remain predictable while reflecting
the current lockfile versions.

In `@data_models/specs/data_models/PlannedWorkouts.tsp`:
- Around line 395-396: The Created/Updated timestamp fields in the
PlannedWorkouts model use plain string types; change the types of created_at and
last_updated_at from string to the utcDateTime scalar to strengthen typing and
ensure RFC3339-encoded OpenAPI output—update the PlannedWorkouts type definition
to reference utcDateTime for those two fields and run any schema/type checks to
confirm imports and scalar declarations (ensure the utcDateTime scalar is
available in the schema).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99d55913-6297-4e47-a600-0dc98fd9fa3f

📥 Commits

Reviewing files that changed from the base of the PR and between b73e26f and 1670f4e.

⛔ Files ignored due to path filters (1)
  • data_models/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • .github/workflows/release.yml
  • data_models/package.json
  • data_models/specs/data_models/PlannedWorkouts.tsp
  • data_models/specs/data_models/tspconfig.yaml
  • workouts.yaml

Comment on lines +238 to +239
intensity_targets?: IntensityTarget[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and examine the relevant sections
find . -name "PlannedWorkouts.tsp" -type f

Repository: tryterra/openapi

Length of output: 111


🏁 Script executed:

# Get file content around the mentioned lines to verify the arrays
if [ -f "data_models/specs/data_models/PlannedWorkouts.tsp" ]; then
  wc -l "data_models/specs/data_models/PlannedWorkouts.tsp"
  echo "=== Lines 235-260 ==="
  sed -n '235,260p' "data_models/specs/data_models/PlannedWorkouts.tsp"
  echo "=== Lines 275-285 ==="
  sed -n '275,285p' "data_models/specs/data_models/PlannedWorkouts.tsp"
fi

Repository: tryterra/openapi

Length of output: 1125


🌐 Web query:

TypeSpec @Maxitemsdecorator support and@typespec/openapi3 maxItems emission

💡 Result:

  • TypeSpec support: @maxItems(value) is a built-in TypeSpec decorator. It targets array types (unknown[]) or ModelProperty. Example from the docs: @maxItems(5) model Endpoints is string[]; and you can also apply it to a property like @maxItems(10) tags: string[];. [2]
  • @typespec/openapi3 emission: The OpenAPI v3 emitter maps TypeSpec’s built-in @maxItems(value) to the OpenAPI/JSON Schema keyword maxItems: value on the emitted array schema. [1]

Add explicit array bounds on request models.

These user-supplied arrays are unbounded, which can create expensive payloads and validation pressure. Add @maxItems(...) limits aligned with product constraints.

Suggested change
 model WorkoutStep {
   /** Target intensities for this step (heart rate, power, pace, etc.) */
+  `@maxItems`(20)
   intensity_targets?: IntensityTarget[];
 }

 model StepBlock {
   /** Steps within this block */
+  `@maxItems`(200)
   steps: WorkoutStep[];
 }

 model WorkoutTemplate {
   /** Blocks of steps that make up the workout */
+  `@maxItems`(50)
   step_blocks: StepBlock[];
 }

Also applies to: 253-254, 278-279

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data_models/specs/data_models/PlannedWorkouts.tsp` around lines 238 - 239,
The PlannedWorkouts request model exposes unbounded arrays (e.g., the
intensity_targets?: IntensityTarget[] property) which need explicit
`@maxItems`(...) bounds; update the PlannedWorkouts model to add a
`@maxItems`(limit) decorator on intensity_targets and the other user-supplied
array properties mentioned in the review (the two other array fields around the
same area) using product-aligned limits, ensure the appropriate validation
decorator/import is present, and run/adjust tests or schema generation to
confirm the maxItems constraint is applied.

Comment on lines +473 to +474
@query start_date?: string,
@query end_date?: string
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the actual file and context
cd /tmp && find . -name "PlannedWorkouts.tsp" -type f 2>/dev/null | head -5

Repository: tryterra/openapi

Length of output: 42


🏁 Script executed:

# Search for files in the repository
git ls-files | grep -i plannedworkout

Repository: tryterra/openapi

Length of output: 109


🏁 Script executed:

# Look for TypeSpec files to understand the structure
git ls-files | grep "\.tsp$" | head -20

Repository: tryterra/openapi

Length of output: 678


🌐 Web query:

TypeSpec @patterndecorator with@query parameters OpenAPI constraint emission

💡 Result:

In TypeSpec, @pattern(...) is a data validation decorator for string | bytes | ModelProperty (so it can be applied directly to an operation parameter, including one marked with @query). When you emit OpenAPI with @typespec/openapi3, @pattern(regex) is emitted as the OpenAPI/JSON Schema keyword pattern: <regex> in the parameter’s schema. [1], [2]

Example:

import "@typespec/http";
import "@typespec/openapi3";
using Http;

`@pattern`("^[A-Z]{2}$")
scalar StateCode extends string;

op list(`@query` state: StateCode): void;
// (equivalently) op list(`@query` `@pattern`("^[A-Z]{2}$") state: string): void;

Emits (conceptually):

parameters:
  - in: query
    name: state
    schema:
      type: string
      pattern: "^[A-Z]{2}$"

Notes:

  • The optional validationMessage on @pattern(pattern, validationMessage?) is emitter-defined (“may choose to use”); OpenAPI emission typically only needs the pattern keyword. [1]

Sources:
[1] TypeSpec “Built-in Decorators” — @pattern definition/targets and optional message behavior: https://typespec.io/docs/standard-library/built-in-decorators/
[2] TypeSpec “OpenAPI v3 emitter” — validation mapping @pattern(regex)pattern: regex and @query as query parameter location: https://typespec.io/docs/emitters/openapi3/openapi/


🏁 Script executed:

# Search for existing `@pattern` usage in the codebase
rg "@pattern" --type tsp -B 2 -A 2

Repository: tryterra/openapi

Length of output: 87


🏁 Script executed:

# Read the PlannedWorkouts.tsp file to see the context
wc -l data_models/specs/data_models/PlannedWorkouts.tsp

Repository: tryterra/openapi

Length of output: 113


🏁 Script executed:

# Read the specific section around lines 473-474
sed -n '450,500p' data_models/specs/data_models/PlannedWorkouts.tsp

Repository: tryterra/openapi

Length of output: 1212


🏁 Script executed:

# Search for date format documentation in the file
grep -n "YYYY-MM-DD\|yyyy-mm-dd\|date format" data_models/specs/data_models/PlannedWorkouts.tsp

Repository: tryterra/openapi

Length of output: 255


🏁 Script executed:

# Look for similar `@pattern` or date validation in other TypeSpec files
grep -r "YYYY-MM-DD\|yyyy-mm-dd\|pattern.*date" --include="*.tsp" data_models/

Repository: tryterra/openapi

Length of output: 393


Apply @pattern constraint to date query parameters.

The endpoint documentation specifies YYYY-MM-DD format (line 466), but the start_date and end_date parameters currently accept arbitrary strings. Apply the @pattern decorator to enforce the documented date format:

   op listPlannedWorkouts(
     ...TerraHeaders,
     `@query` user_id: string,
-    `@query` start_date?: string,
-    `@query` end_date?: string
+    `@query` `@pattern`("^\\d{4}-\\d{2}-\\d{2}$") start_date?: string,
+    `@query` `@pattern`("^\\d{4}-\\d{2}-\\d{2}$") end_date?: string
   ): ListPlannedWorkoutsResponse | {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@query start_date?: string,
@query end_date?: string
op listPlannedWorkouts(
...TerraHeaders,
`@query` user_id: string,
`@query` `@pattern`("^\\d{4}-\\d{2}-\\d{2}$") start_date?: string,
`@query` `@pattern`("^\\d{4}-\\d{2}-\\d{2}$") end_date?: string
): ListPlannedWorkoutsResponse | {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data_models/specs/data_models/PlannedWorkouts.tsp` around lines 473 - 474,
The start_date and end_date query parameters in PlannedWorkouts.tsp currently
accept any string; add a `@pattern` decorator enforcing the YYYY-MM-DD format
(e.g. regex ^\d{4}-\d{2}-\d{2}$) to both `@query` start_date and `@query` end_date
declarations so the specs validate dates against the documented format; update
the `@query` lines for start_date and end_date in PlannedWorkouts.tsp to include
the `@pattern` constraint.

- Add GET /workouts (list all templates)
- Add GET /workouts/{workout_id} (get template by ID)
- Add DELETE /workouts/{workout_id} (delete template)
- Add WorkoutTemplateWithId model with workout_id for GET responses
- Regenerate workouts.yaml OpenAPI spec

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
data_models/specs/data_models/PlannedWorkouts.tsp (2)

537-538: ⚠️ Potential issue | 🟡 Minor

Enforce documented date format on list filters.

start_date and end_date are documented as YYYY-MM-DD but currently accept arbitrary strings (Line [537]-Line [538]).

Proposed fix
   op listPlannedWorkouts(
     ...TerraHeaders,
     `@query` user_id: string,
-    `@query` start_date?: string,
-    `@query` end_date?: string
+    `@query` `@pattern`("^\\d{4}-\\d{2}-\\d{2}$") start_date?: string,
+    `@query` `@pattern`("^\\d{4}-\\d{2}-\\d{2}$") end_date?: string
   ): ListPlannedWorkoutsResponse | {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data_models/specs/data_models/PlannedWorkouts.tsp` around lines 537 - 538,
The list filter query parameters start_date and end_date in PlannedWorkouts.tsp
currently accept arbitrary strings; update the `@query` parameter validation for
start_date and end_date to enforce the documented YYYY-MM-DD format (e.g.,
validate with a strict regex like /^\d{4}-\d{2}-\d{2}$/ or parse-and-validate
using a date utility), return a 400/validation error when the format is invalid,
and include the validation on the same symbols (`@query` start_date?: string and
`@query` end_date?: string) so only properly formatted dates are accepted.

237-239: ⚠️ Potential issue | 🟠 Major

Bound user-controlled arrays with explicit @maxItems.

These request-path arrays are currently unbounded (Line [238], Line [253], Line [278]), which allows oversized payloads and weakens validation limits.

Proposed fix
 model WorkoutStep {
   /** Target intensities for this step (heart rate, power, pace, etc.) */
+  `@maxItems`(20)
   intensity_targets?: IntensityTarget[];
 }

 model StepBlock {
   /** Steps within this block */
+  `@maxItems`(200)
   steps: WorkoutStep[];
 }

 model WorkoutTemplate {
   /** Blocks of steps that make up the workout */
+  `@maxItems`(50)
   step_blocks: StepBlock[];
 }

Also applies to: 252-254, 277-279

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data_models/specs/data_models/PlannedWorkouts.tsp` around lines 237 - 239,
Several request-path array properties in the PlannedWorkouts model (notably
intensity_targets: IntensityTarget[] and the other two request arrays flagged in
the review) are unbounded; add explicit TypeSpec `@maxItems` annotations to each
array declaration (e.g., `@maxItems`(<reasonable_limit>)) to enforce an upper
bound, update any related schema/validators to honor the new limits, and
run/adjust tests to reflect the new maximums. Ensure you modify the declarations
for intensity_targets and the other two array properties in PlannedWorkouts.tsp
rather than changing call sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@data_models/specs/data_models/PlannedWorkouts.tsp`:
- Around line 290-297: The CreateWorkoutTemplateRequest currently inherits
WorkoutTemplate which includes computed read-only fields
(estimated_duration_seconds, estimated_distance_meters, estimated_calories)
defined in PlannedWorkouts.tsp; to fix this, introduce a separate input model
(e.g., WorkoutTemplateCreate or WorkoutTemplateInput) that contains only
writable properties and have CreateWorkoutTemplateRequest use that instead of
inheriting WorkoutTemplate, then keep the computed fields only on the output
model (e.g., WorkoutTemplate or WorkoutTemplateResponse) and mark them readOnly
in the schema so they are not accepted in create requests.

---

Duplicate comments:
In `@data_models/specs/data_models/PlannedWorkouts.tsp`:
- Around line 537-538: The list filter query parameters start_date and end_date
in PlannedWorkouts.tsp currently accept arbitrary strings; update the `@query`
parameter validation for start_date and end_date to enforce the documented
YYYY-MM-DD format (e.g., validate with a strict regex like /^\d{4}-\d{2}-\d{2}$/
or parse-and-validate using a date utility), return a 400/validation error when
the format is invalid, and include the validation on the same symbols (`@query`
start_date?: string and `@query` end_date?: string) so only properly formatted
dates are accepted.
- Around line 237-239: Several request-path array properties in the
PlannedWorkouts model (notably intensity_targets: IntensityTarget[] and the
other two request arrays flagged in the review) are unbounded; add explicit
TypeSpec `@maxItems` annotations to each array declaration (e.g.,
`@maxItems`(<reasonable_limit>)) to enforce an upper bound, update any related
schema/validators to honor the new limits, and run/adjust tests to reflect the
new maximums. Ensure you modify the declarations for intensity_targets and the
other two array properties in PlannedWorkouts.tsp rather than changing call
sites.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eba37e53-b49d-431b-a5ed-011bd049cdc6

📥 Commits

Reviewing files that changed from the base of the PR and between 1670f4e and 414f9e9.

📒 Files selected for processing (2)
  • data_models/specs/data_models/PlannedWorkouts.tsp
  • workouts.yaml

Comment on lines +290 to +297
/** Estimated total duration in seconds (read-only, populated on retrieval) */
estimated_duration_seconds?: float32;

/** Estimated total distance in meters (read-only, populated on retrieval) */
estimated_distance_meters?: float32;

/** Estimated total calories (read-only, populated on retrieval) */
estimated_calories?: float32;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "PlannedWorkouts.tsp" -type f

Repository: tryterra/openapi

Length of output: 111


🏁 Script executed:

cat -n ./data_models/specs/data_models/PlannedWorkouts.tsp | sed -n '280,320p'

Repository: tryterra/openapi

Length of output: 1621


🏁 Script executed:

cat -n ./data_models/specs/data_models/PlannedWorkouts.tsp | sed -n '240,300p'

Repository: tryterra/openapi

Length of output: 2246


🏁 Script executed:

# Check if there are other request models that might have similar issues
rg "model.*Request.*is.*Workout" ./data_models/specs/data_models/PlannedWorkouts.tsp

Repository: tryterra/openapi

Length of output: 116


🏁 Script executed:

# Check if there's any OpenAPI schema generation or output that shows the issue
fd -e "json" -e "yaml" -e "yml" | xargs grep -l "CreateWorkoutTemplateRequest" 2>/dev/null | head -5

Repository: tryterra/openapi

Length of output: 73


🏁 Script executed:

rg -A 30 "CreateWorkoutTemplateRequest:" ./workouts.yaml | head -50

Repository: tryterra/openapi

Length of output: 1029


🏁 Script executed:

rg -A 50 "CreateWorkoutTemplateRequest:" ./workouts.yaml | head -70

Repository: tryterra/openapi

Length of output: 1740


Create request exposes read-only computed fields in OpenAPI schema.

CreateWorkoutTemplateRequest inherits from WorkoutTemplate, which includes fields documented as read-only (lines 290–297). The generated OpenAPI schema incorrectly exposes estimated_duration_seconds, estimated_distance_meters, and estimated_calories in the create request, allowing clients to attempt setting computed fields. Separate input and output models so the create request only accepts writable fields.

Proposed refactor
+model WorkoutTemplateInput {
+  name: string;
+  sport: SportType;
+  step_blocks: StepBlock[];
+  description?: string;
+  environment?: EnvironmentType;
+  `@minValue`(0)
+  pool_length_meters?: float32;
+}
+
-model WorkoutTemplate {
+model WorkoutTemplate is WorkoutTemplateInput {
-  /** Workout name/title */
-  name: string;
-  /** Sport/activity type */
-  sport: SportType;
-  /** Blocks of steps that make up the workout */
-  step_blocks: StepBlock[];
-  /** Optional workout description */
-  description?: string;
-  /** Indoor/outdoor/pool environment */
-  environment?: EnvironmentType;
-  /** For lap_swimming: pool length in meters (can be overridden at scheduling time) */
-  `@minValue`(0)
-  pool_length_meters?: float32;
   /** Estimated total duration in seconds (read-only, populated on retrieval) */
   estimated_duration_seconds?: float32;
   /** Estimated total distance in meters (read-only, populated on retrieval) */
   estimated_distance_meters?: float32;
   /** Estimated total calories (read-only, populated on retrieval) */
   estimated_calories?: float32;
 }
 
-model CreateWorkoutTemplateRequest is WorkoutTemplate {}
+model CreateWorkoutTemplateRequest is WorkoutTemplateInput {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data_models/specs/data_models/PlannedWorkouts.tsp` around lines 290 - 297,
The CreateWorkoutTemplateRequest currently inherits WorkoutTemplate which
includes computed read-only fields (estimated_duration_seconds,
estimated_distance_meters, estimated_calories) defined in PlannedWorkouts.tsp;
to fix this, introduce a separate input model (e.g., WorkoutTemplateCreate or
WorkoutTemplateInput) that contains only writable properties and have
CreateWorkoutTemplateRequest use that instead of inheriting WorkoutTemplate,
then keep the computed fields only on the output model (e.g., WorkoutTemplate or
WorkoutTemplateResponse) and mark them readOnly in the schema so they are not
accepted in create requests.

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.

1 participant