Skip to content

feat(serve): add strict POST /merge endpoint#240

Closed
hfl0506 wants to merge 0 commit into
goptics:mainfrom
hfl0506:feat/rest-merge-endpoint
Closed

feat(serve): add strict POST /merge endpoint#240
hfl0506 wants to merge 0 commit into
goptics:mainfrom
hfl0506:feat/rest-merge-endpoint

Conversation

@hfl0506

@hfl0506 hfl0506 commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

Closes #215

Changes

  • Added strict POST /merge handling with tagAxis support.

  • Preserved Dataset wire format and always returns an array.

  • Added nested strict validation and structured Problem Details
    errors.

  • Added real merge-axis conflict detection in the shared core.

  • Expanded api/openapi.yaml with schemas and examples.

  • Added handler, behavior, core, and contract tests.

Test Result

Case 1

printf '%s\n' '{
      "datasets": [
        {
          "name": "Bench",
          "tag": "v1",
          "timestamp": "2026-01-01T00:00:00Z",
          "axes": [{"key": "y"}],

          "settings": [],
          "data": [{"yAxis": "10"}]
        },
        {
          "name": "Bench",
          "tag": "v2",
          "timestamp": "2026-02-01T00:00:00Z",
          "axes": [{"key": "y"}],

          "settings": [],
          "data": [{"yAxis": "20"}]
        }
      ],
      "tagAxis": "x"
    }' | curl -sS http://127.0.0.1:8080/merge \
          -H 'Content-Type: application/json' \
          -H 'Accept: application/json' \
          --data-binary @- | jq .
          
#result 
[
  {
    "tag": "v2",
    "timestamp": "2026-02-01T00:00:00Z",
    "name": "Bench",
    "history": [
      {
        "tag": "v1",
        "timestamp": "2026-01-01T00:00:00Z"
      }
    ],
    "axes": [
      {
        "key": "x"
      },
      {
        "key": "y"
      }
    ],
    "settings": [],
    "data": [
      {
        "xAxis": "v1",
        "yAxis": "10"
      },
      {
        "xAxis": "v2",
        "yAxis": "20"
      }
    ]
  }
]

Case 3

printf '%s\n' '{
    "datasets": [
      {
        "name": "Bench",
        "tag": "v1",
        "axes": [{"key": "y"}],
        "settings": [],
        "data": [{"yAxis": "10"}]
      },
      {
        "name": "Bench",
        "tag": "v2",
        "axes": [{"key": "y"}],
        "settings": [],
        "data": [{"yAxis": "20"}]
      }
    ]
  }' | curl -sS http://127.0.0.1:8080/merge \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    --data-binary @- | jq '.[0].data'
    
 #result
 [
  {
    "name": "v1",
    "yAxis": "10"                  },
  {
    "name": "v2",
    "yAxis": "20"
  }
]

Case 3

printf '%s\n' '{
    "datasets": [
      {"name":"Bench","axes":[],"settings":[],"data":[]}
    ]
  }' | curl -i http://127.0.0.1:8080/merge \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    --data-binary @-
 
#result
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Date: Sat, 18 Jul 2026 22:10:19 GMT
Content-Length: 269
{"detail":"Request validation failed.","errors":[{"location":"body","path":"/datasets","code":"min_items","message":"at least two datasets are required"}],"instance":"/merge","status":400,"title":"Invalid request","type":"https://vizb.goptics.org/problems/validation"}

Case 4

printf '%s\n' '{
    "datasets": [
      {
        "name":"Latency",
        "tag":"v1",
        "axes":[],
        "settings":[],
        "data":[{"name":"request"}]
      },
      {
        "name":"Throughput",
        "tag":"v1",
        "axes":[],
        "settings":[],
        "data":[{"name":"request"}]
      }
    ]
  }' | curl -sS http://127.0.0.1:8080/merge \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    --data-binary @- | jq 'map(.name)'

#result
[
    "Latency",
    "Throughput"
  ]

Summary by CodeRabbit

  • New Features
    • Added vizb serve local REST API for conversion, merging, and UI rendering.
    • Added end-to-end support for integer CLI/chart/data options.
  • Bug Fixes
    • Merge now rejects datasets with conflicting axis types and reports clear processing errors.
    • Chart configuration now enforces each chart type appears at most once per selection/settings.
  • Documentation
    • Updated server documentation with request limits, timeouts, shutdown behavior, and example payloads.
  • Tests / Contract Updates
    • Expanded OpenAPI and endpoint contract assertions, including new 406 handling and merge conflict examples.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a serve REST API with strict convert, merge, and UI contracts, lifecycle handling, OpenAPI coverage, merge conflict validation, and integer chart-flag support across CLI binding, conversion, chart seeding, and reset behavior.

Changes

Serve REST API

Layer / File(s) Summary
OpenAPI endpoint and schema contracts
api/openapi.yaml, api/openapi_test.go
Updates merge responses and examples, adds a shared merge response schema, and enforces unique chart configuration types.
Merge conflict validation
pkg/core/core.go, pkg/core/core_test.go
Rejects merges with conflicting axis types for datasets sharing a name.
Strict request and dataset contracts
cmd/serve_contract.go
Adds strict request decoding, option validation, chart materialisation, and dataset decoding for convert, merge, and UI requests.
Server lifecycle and endpoint handlers
cmd/serve.go
Adds the Cobra server command, HTTP lifecycle management, convert/merge/UI handlers, content negotiation, and Problem+JSON responses.
Server behavior validation and documentation
cmd/serve_test.go, cmd/testing.go, docs/src/content/docs/commands/serve.mdx
Covers routing, lifecycle, endpoint validation, shutdown, and documents request limits, timeouts, and shutdown behavior.

Integer chart flags

Layer / File(s) Summary
Integer flag value support
internal/flags/flag.go, cmd/cli/flagbag.go
Adds integer flag metadata, storage, binding, access, chart seeding, and reset support.
Integer chart configuration conversion
shared/chart_spec.go, shared/chart_spec_test.go
Parses integer chart values into typed encoded values and tests invalid input handling.
Integer flag lifecycle tests
cmd/cli/flagbag_test.go
Tests integer accessors, defaults, validation, chart seeds, and reset behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ServeAPI
  participant ContractDecoder
  participant Core
  Client->>ServeAPI: POST /, /merge, or /ui
  ServeAPI->>ContractDecoder: strict decode and validate JSON
  ContractDecoder-->>ServeAPI: validated request or Problem+JSON details
  ServeAPI->>Core: convert or merge datasets
  Core-->>ServeAPI: datasets or processing error
  ServeAPI-->>Client: JSON, HTML, or Problem+JSON response
Loading

Possibly related issues

  • #215 — Directly covers exposing dataset merging through POST /merge, including strict validation, merge behavior, errors, schemas, and tests.
  • #213 — Covers the vizb serve command, HTTP routes, limits, lifecycle handling, and comprehensive tests.

Possibly related PRs

  • goptics/vizb#232 — Overlaps the REST server implementation, endpoint contracts, validation, and tests.
  • goptics/vizb#239 — Overlaps the OpenAPI response, description, chart uniqueness, and contract-test changes.

Suggested labels: cli

Suggested reviewers: fahimfaisaal

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also adds unrelated serve/convert/ui, CLI flag, and docs changes beyond #215's merge-endpoint scope. Split unrelated serve/convert/ui, CLI flag, and docs updates into separate PRs so this change set focuses on POST /merge and supporting merge-core work.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR meets #215 by adding strict POST /merge handling, schema/docs updates, merge-conflict errors, and tests for success and failure cases.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a strict POST /merge endpoint.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@hfl0506 hfl0506 added enhancement New feature or request api api related works labels Jul 18, 2026
@codecov-commenter

codecov-commenter commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@hfl0506
hfl0506 marked this pull request as ready for review July 19, 2026 00:53
@hfl0506
hfl0506 requested a review from fahimfaisaal as a code owner July 19, 2026 00:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
cmd/serve_test.go (1)

28-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the shared JSON fixtures on ServeSuite.

Move these payload fixtures onto the suite and initialize them in suite setup rather than maintaining package-level shared fixtures.

As per coding guidelines, “Put shared fixtures and setup on the Go test suite.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/serve_test.go` around lines 28 - 32, Move validDatasetJSON,
firstMergeJSON, and secondMergeJSON from package-level constants onto
ServeSuite, and initialize them during the suite setup lifecycle. Update all
tests to reference the suite-owned fixture fields while preserving their
existing payload values.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/openapi.yaml`:
- Around line 409-453: Complete the chart-config uniqueness contract by reusing
the existing per-type constraints from the shown configs schema in
ChartSelection.configs within api/openapi.yaml (anchor 409-453). Update
api/openapi_test.go (sibling 167-188) to reject duplicate configurations for
every supported chart type—bar, line, scatter, pie, heatmap, and radar—in both
Dataset and request schema locations.

In `@cmd/serve_test.go`:
- Around line 144-224: Update the lifecycle tests
TestCancellationShutsDownInMemoryListener,
TestCancellationTriggersGracefulShutdown, TestShutdownFailureIsReturned, and
TestServeFailureDuringShutdownIsReturned to replace direct receives from
acceptStarted and result with the suite’s timeout-aware assertion/helper. Ensure
both listener startup synchronization and runServer completion fail promptly
when they do not occur, while preserving the existing error and shutdown
assertions.

In `@cmd/serve.go`:
- Around line 85-90: Update composeRESTRoutes to add explicit fallback handling
for unmatched paths and method mismatches, routing both through writeAPIProblem
so responses use the documented application/problem+json format instead of
ServeMux plain text. Preserve the existing convert, merge, and ui routes, and
add route tests covering unknown paths and unsupported methods.

In `@docs/src/content/docs/commands/serve.mdx`:
- Around line 26-29: Update runServer’s shutdown handling so an error returned
by Shutdown cannot leave in-flight work running beyond the documented 10-second
bound. Ensure the shutdown context or timeout is enforced through the failure
path, while preserving graceful completion for work that finishes within the
limit.

---

Nitpick comments:
In `@cmd/serve_test.go`:
- Around line 28-32: Move validDatasetJSON, firstMergeJSON, and secondMergeJSON
from package-level constants onto ServeSuite, and initialize them during the
suite setup lifecycle. Update all tests to reference the suite-owned fixture
fields while preserving their existing payload values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d6b3f3ef-7984-4f93-872f-a408bd5958dd

📥 Commits

Reviewing files that changed from the base of the PR and between 914ada1 and 67c2334.

📒 Files selected for processing (14)
  • api/openapi.yaml
  • api/openapi_test.go
  • cmd/cli/flagbag.go
  • cmd/cli/flagbag_test.go
  • cmd/serve.go
  • cmd/serve_contract.go
  • cmd/serve_test.go
  • cmd/testing.go
  • docs/src/content/docs/commands/serve.mdx
  • internal/flags/flag.go
  • pkg/core/core.go
  • pkg/core/core_test.go
  • shared/chart_spec.go
  • shared/chart_spec_test.go

Comment thread api/openapi.yaml
Comment thread cmd/serve_test.go
Comment thread cmd/serve.go Outdated
Comment on lines +26 to +29
Each request body is limited to 10 MiB. The server uses a 5-second header read
timeout, a 15-second request read timeout, and 60-second write and idle
timeouts. On `SIGINT` or `SIGTERM`, it stops accepting requests and allows up
to 10 seconds for in-flight work to finish.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Enforce the documented ten-second shutdown bound.

The current failure path in runServer can leave active work running after Shutdown returns an error, so “up to 10 seconds” is not presently guaranteed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/content/docs/commands/serve.mdx` around lines 26 - 29, Update
runServer’s shutdown handling so an error returned by Shutdown cannot leave
in-flight work running beyond the documented 10-second bound. Ensure the
shutdown context or timeout is enforced through the failure path, while
preserving graceful completion for work that finishes within the limit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
api/openapi.yaml (1)

374-421: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract duplicate chart-config constraints into a shared component.

The uniqueness constraints for chart configurations (the allOf/contains block) are perfectly implemented, but they are identical here and in Dataset.settings (lines 451-497). Extracting this into a reusable schema component will eliminate duplication and ensure both arrays remain consistently validated if a new chart type is added in the future.

♻️ Proposed refactoring

Add a new schema component, for example, ChartConfigList:

    ChartConfigList:
      type: array
      description: Each chart type may appear at most once.
      items:
        $ref: '`#/components/schemas/ChartConfig`'
      allOf:
        - contains:
            type: object
            required: [type]
            properties:
              type: { const: bar }
          minContains: 0
          maxContains: 1
        - contains:
            type: object
            required: [type]
            properties:
              type: { const: line }
          minContains: 0
          maxContains: 1
        # ... (include all other chart types)

Then reference it in both locations. In ChartSelection:

         types:
           # ...
-        configs:
-          type: array
-          description: Each chart type may appear at most once.
-          items:
-            $ref: '`#/components/schemas/ChartConfig`'
-          allOf:
-            - contains:
-                type: object
-                required: [type]
-                properties:
-                  type: { const: bar }
-              minContains: 0
-              maxContains: 1
-            # ... (remaining types)
+        configs:
+          $ref: '`#/components/schemas/ChartConfigList`'

And similarly in Dataset:

         axes:
           type: array
           items: { $ref: '`#/components/schemas/Axis`' }
-        settings:
-          type: array
-          description: Each chart type may appear at most once.
-          items: { $ref: '`#/components/schemas/ChartConfig`' }
-          allOf:
-            - contains:
-                type: object
-                required: [type]
-                properties:
-                  type: { const: bar }
-              minContains: 0
-              maxContains: 1
-            # ... (remaining types)
+        settings:
+          $ref: '`#/components/schemas/ChartConfigList`'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/openapi.yaml` around lines 374 - 421, Extract the duplicated chart-type
uniqueness constraints into a shared components schema, such as ChartConfigList,
preserving its array description, ChartConfig items, and all existing
contains/maxContains rules for every chart type. Replace the inline configs
definitions in ChartSelection and Dataset.settings with references to this
shared schema so both locations use the same validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@api/openapi.yaml`:
- Around line 374-421: Extract the duplicated chart-type uniqueness constraints
into a shared components schema, such as ChartConfigList, preserving its array
description, ChartConfig items, and all existing contains/maxContains rules for
every chart type. Replace the inline configs definitions in ChartSelection and
Dataset.settings with references to this shared schema so both locations use the
same validation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e05d981-e5eb-4cd4-a8ab-92cb013df6ba

📥 Commits

Reviewing files that changed from the base of the PR and between 67c2334 and 783e28c.

📒 Files selected for processing (5)
  • api/openapi.yaml
  • api/openapi_test.go
  • cmd/serve.go
  • cmd/serve_test.go
  • docs/src/content/docs/commands/serve.mdx
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/src/content/docs/commands/serve.mdx
  • api/openapi_test.go
  • cmd/serve.go
  • cmd/serve_test.go

@fahimfaisaal fahimfaisaal added this to the Release v0.16.0 milestone Jul 19, 2026
@fahimfaisaal

Copy link
Copy Markdown
Member

Hi @hfl0506, Thanks for these PRs. You have direct access to this repo, so you don't need to create PRs from the forked one.

You can clone this repo and start working on it.

@hfl0506

hfl0506 commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

Hi @hfl0506, Thanks for these PRs. You have direct access to this repo, so you don't need to create PRs from the forked one.

You can clone this repo and start working on it.

Hi @hfl0506, Thanks for these PRs. You have direct access to this repo, so you don't need to create PRs from the forked one.

You can clone this repo and start working on it.

Hi @fahimfaisaal do I need to recreate these MR or just starting from the new MR ? Thanks

@fahimfaisaal

Copy link
Copy Markdown
Member

@hfl0506 no, not for the current ones; you can follow it from the next PRs

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

Labels

api api related works enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

expose dataset merging through POST /merge

3 participants