Skip to content

feat(api): add root conversion endpoint#239

Merged
fahimfaisaal merged 11 commits into
goptics:mainfrom
hfl0506:feat/rest-root-endpoint
Jul 20, 2026
Merged

feat(api): add root conversion endpoint#239
fahimfaisaal merged 11 commits into
goptics:mainfrom
hfl0506:feat/rest-root-endpoint

Conversation

@hfl0506

@hfl0506 hfl0506 commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

Resolve #214

Changes

  • Added strict POST / Dataset/HTML conversion flow through the shared core.

  • Added top-level Dataset metadata and structured parser, grouping, unit,
    selection, chart, and output fields.

  • Added strict validation for unknown, null, invalid, and inapplicable options.

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

  • Expanded cmd/serve.go, cmd/serve_contract.go, and test coverage.

Test Result

go run . serve --host 127.0.0.1 --port 8080

Case 1

curl -i http://127.0.0.1:8080/ \
          -H 'Content-Type: application/json' \

          -H 'Accept: application/json' \
          --data '{
        "input": "region,latency\nwest,12\neast,18\n",
        "parser": "csv",
        "select": ["region,latency"],
        "charts": {"types": ["bar", "line"]},
        "output": {"format": "dataset"}
      }'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 18 Jul 2026 18:29:56 GMT
Content-Length: 326

{"timestamp":"2026-07-18T18:29:56Z","name":"Comparisons","theme":"default","axes":[{"key":"x","label":"region"},{"key":"y","label":"latency","type":"value"}],"settings":[{"type":"bar","scale":"linear"},{"type":"line","scale":"linear"}],"data":[{"xAxis":"west","yAxis":"12"},{"xAxis":"east","yAxis":"18"}],"preserveRows":true}

Case 2

curl -i http://127.0.0.1:8080/ \
          -H 'Content-Type: application/json' \

          -H 'Accept: application/json' \
          --data '{
        "input": [
          {"region":"west","latency":12},
          {"region":"east","latency":18}
        ],
        "parser":"json",
        "grouping":{"pattern":"x","columns":["region"]},
        "charts":{"types":["bar"]},
        "output":{"format":"dataset"}
      }'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 18 Jul 2026 18:31:46 GMT
Content-Length: 282

{"timestamp":"2026-07-18T18:31:46Z","name":"Comparisons","theme":"default","axes":[{"key":"x","label":"region"}],"settings":[{"type":"bar","scale":"linear"}],"data":[{"xAxis":"west","stats":[{"type":"latency","value":12}]},{"xAxis":"east","stats":[{"type":"latency","value":18}]}]}

Case 3

curl -i http://127.0.0.1:8080/ \
          -H 'Content-Type: application/json' \

          -H 'Accept: application/json' \
          --data '{
        "input":"region,value\nwest,12\n",
        "id":"test-dataset",
        "name":"Local test",
        "description":"Testing REST conversion",
        "tag":"v1",
        "theme":"#5470C6,#3BA272",
        "parser":"csv",
        "charts":{"types":["bar"]}
      }'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 18 Jul 2026 18:32:55 GMT
Content-Length: 301

{"id":"test-dataset","tag":"v1","timestamp":"2026-07-18T18:32:55Z","name":"Local test","theme":"#5470C6,#3BA272","description":"Testing REST conversion","axes":[{"key":"x","label":"region"}],"settings":[{"type":"bar","scale":"linear"}],"data":[{"xAxis":"west","stats":[{"type":"value","value":12}]}]}

Case 4

curl -i http://127.0.0.1:8080/ \
          -H 'Content-Type: application/json' \

          -H 'Accept: application/json' \
          --data '{
        "input":"BenchmarkFoo-8 100 123 ns/op\n",
        "parser":"go",
        "charts":{"types":["bar"]}
      }'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 18 Jul 2026 18:33:40 GMT
Content-Length: 223

{"timestamp":"2026-07-18T18:33:40Z","name":"Comparisons","theme":"default","axes":[{"key":"x"}],"settings":[{"type":"bar","scale":"linear"}],"data":[{"xAxis":"Foo","stats":[{"type":"Execution Time (ns/op)","value":123}]}]}

Case 5

curl -sS http://127.0.0.1:8080/ \
    -H 'Content-Type: application/json' \
    -H 'Accept: text/html' \
    --data '{
      "input":"region,value\nwest,12\neast,18\n",
      "parser":"csv",
      "charts":{"types":["bar"]},
      "output":{"format":"html"}
    }' \
    --output /tmp/vizb.html

  open /tmp/vizb.html
Screenshot 2026-07-18 at 11 35 03 AM ### Case 6 ```bash curl -i http://127.0.0.1:8080/ \ -H 'Content-Type: application/json' \ --data '{ "input":"x,y\na,1\n", "unknownOption":true }' HTTP/1.1 400 Bad Request Content-Type: application/problem+json Date: Sat, 18 Jul 2026 18:36:24 GMT Content-Length: 274

{"detail":"Request validation failed.","errors":[{"location":"body","path":"/unknownOption","code":"unknown_field","message":"unknown request field unknownOption"}],"instance":"/","status":400,"title":"Invalid request","type":"https://vizb.goptics.org/problems/validation"}

### Case 7
```bash
curl -i http://127.0.0.1:8080/ \
          -H 'Content-Type: application/json' \

          --data '{
        "input":"x,y\na,1\n",
        "units":{"memory":null}
      }'
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Date: Sat, 18 Jul 2026 18:36:54 GMT
Content-Length: 267

{"detail":"Request validation failed.","errors":[{"location":"body","path":"/units/memory","code":"invalid_type","message":"/units/memory must not be null"}],"instance":"/","status":400,"title":"Invalid request","type":"https://vizb.goptics.org/problems/validation"}

Case 8

curl -i http://127.0.0.1:8080/ \
          -H 'Content-Type: application/json' \

          --data '{
        "input":"region,value\nwest,1\n",
        "parser":"csv",
        "charts":{
          "types":["bar"],
          "configs":[{"type":"bar","threeDRotate":true}]
        }
      }'
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Date: Sat, 18 Jul 2026 18:37:29 GMT
Content-Length: 323

{"detail":"Request validation failed.","errors":[{"location":"body","path":"/charts/configs/0/threeDRotate","code":"inapplicable_option","message":"flag \"3d-rotate\" skipped: requires axis \"z\" (present: [x])"}],"instance":"/","status":400,"title":"Invalid request","type":"https://vizb.goptics.org/problems/validation"}

Case 9

curl -i http://127.0.0.1:8080/ \
          -H 'Content-Type: application/json' \

          -H 'Accept: text/html' \
          --data '{
        "input":"x,y\na,1\n",
        "output":{"format":"dataset"}
      }'
HTTP/1.1 406 Not Acceptable
Content-Type: application/problem+json
Date: Sat, 18 Jul 2026 18:38:04 GMT
Content-Length: 159

{"detail":"Accept must allow application/json","instance":"/","status":406,"title":"Not acceptable","type":"https://vizb.goptics.org/problems/not-acceptable"}

Case 10

curl -i http://127.0.0.1:8080/ \
          -H 'Content-Type: text/plain' \
          --data '{"input":"x,y\na,1\n"}'
HTTP/1.1 415 Unsupported Media Type
Content-Type: application/problem+json
Date: Sat, 18 Jul 2026 18:38:32 GMT
Content-Length: 178

{"detail":"Content-Type must be application/json","instance":"/","status":415,"title":"Unsupported media type","type":"https://vizb.goptics.org/problems/unsupported-media-type"}

Case 11

 curl -i http://127.0.0.1:8080/ \
          -H 'Content-Type: application/json' \

          --data '{
        "input":"region,value\nwest,\"unterminated\n",
        "parser":"csv",
        "charts":{"types":["bar"]}
      }'
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
Date: Sat, 18 Jul 2026 18:39:16 GMT
Content-Length: 214

{"detail":"read CSV: parse error on line 2, column 20: extraneous or missing \" in quoted-field","instance":"/","status":422,"title":"Input processing failed","type":"https://vizb.goptics.org/problems/processing"}

Summary by CodeRabbit

  • New Features

    • Conversion requests and responses now support dataset metadata fields (ID, name, description, tag, theme), with updated examples including JSON-to-HTML.
    • Line/scatter chart series symbols now use a validated symbol set.
  • Bug Fixes

    • Stricter API request decoding/validation, including clearer validation error paths and improved handling of null/empty inputs.
    • More consistent conversion/merge error reporting and defaults (including output format behavior).
    • Improved server shutdown behavior for more reliable termination.
  • Documentation

    • Updated serve command example payloads and selector format to match the new metadata fields.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10b581df-2147-434f-9a7e-54c2a49a992e

📥 Commits

Reviewing files that changed from the base of the PR and between 88f1794 and 61dcec5.

📒 Files selected for processing (2)
  • cmd/serve_contract.go
  • cmd/serve_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • cmd/serve_test.go
  • cmd/serve_contract.go

📝 Walkthrough

Walkthrough

Adds a structured root conversion API contract with strict JSON validation, typed option errors, chart symbol validation, shared theme handling, revised shutdown and merge behavior, expanded tests, and updated serve documentation.

Changes

Serve REST API

Layer / File(s) Summary
OpenAPI conversion contract
api/openapi.yaml, api/openapi_test.go
Adds top-level dataset fields, JSON-to-HTML examples, chart symbol validation, grouping constraints, response metadata, and root conversion contract tests.
Shared option and theme foundations
pkg/core/*, pkg/style/*, cmd/cli/*, shared/chart_spec_test.go
Introduces typed conversion option errors, shared theme normalization and validation, CLI integration, and supporting tests.
Strict request and chart contracts
cmd/serve_contract.go
Adds strict decoding, null and unknown-field checks, metadata and parser construction, chart configuration validation, and structured option-path errors.
Conversion and merge endpoint behavior
cmd/serve.go
Updates input classification, output format defaults, conversion error mapping, merge tag-axis validation, decode errors, and shutdown cleanup.
Server behavior tests and documentation
cmd/serve_test.go, docs/src/content/docs/commands/serve.mdx
Expands lifecycle, conversion, merge, UI, decoding, and validation coverage and updates the documented request example.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant handleConvertWithGenerator
  participant core.Convert
  participant HTTPResponse
  Client->>handleConvertWithGenerator: POST / conversion request
  handleConvertWithGenerator->>core.Convert: validated ConvertInput
  core.Convert-->>handleConvertWithGenerator: Dataset, HTML, warnings, or OptionError
  handleConvertWithGenerator-->>HTTPResponse: response or validation problem
  HTTPResponse-->>Client: JSON or HTML result
Loading

Possibly related PRs

  • goptics/vizb#232: Earlier serve API contracts and request validation extended by this change.
  • goptics/vizb#240: Overlapping /merge tagAxis validation changes.

Suggested labels: cli

Suggested reviewers: fahimfaisaal

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the root conversion endpoint.
Linked Issues check ✅ Passed The PR implements POST / with top-level dataset fields, structured options, validation, OpenAPI updates, and tests matching #214.
Out of Scope Changes check ✅ Passed The extra CLI, style, and docs changes appear supportive of the same conversion/theme contract rather than unrelated scope.
✨ 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 18, 2026 21:59
@hfl0506
hfl0506 requested a review from fahimfaisaal as a code owner July 18, 2026 21:59

@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 (2)
api/openapi_test.go (1)

109-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise uniqueness for every chart type.

The schema manually repeats six contains constraints, but this test only proves bar. Iterate through all supported types so a typo in another constraint cannot pass unnoticed.

As per coding guidelines, “After every change, assess affected-feature coverage and add or update tests when needed.”

🤖 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_test.go` around lines 109 - 130, Update
TestDatasetSettingsRequireUniqueChartTypes to verify duplicate settings for
every supported chart type, rather than only bar. Iterate over the complete
supported chart-type set, construct two settings entries with the current type,
and assert validation returns the expected “want at most 1” error for each
iteration.

Source: Coding guidelines

pkg/core/core_test.go (1)

125-154: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert each exact OptionError.Name.

NotEmpty allows every case to return the same incorrect name, which would misroute structured API error paths. Add expected names for select, swap, and grouping; ideally cover filter and jsonPath too.

As per coding guidelines, “After every change, assess affected-feature coverage and add or update tests when needed.”

🤖 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 `@pkg/core/core_test.go` around lines 125 - 154, The
TestConvertIdentifiesInapplicableOptions test currently only checks that
OptionError.Name is non-empty; update each test case to carry and assert its
exact expected name, including select, swap, and grouping. Extend coverage with
filter and jsonPath cases if supported by the existing Convert error paths,
while preserving the current errors.Is assertion.

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 608-610: Update the OpenAPI `symbol` schemas near the `symbol`
properties to constrain values to the server-supported ECharts built-in symbols
and valid `image://`, `path://`, or SVG path formats, instead of allowing
arbitrary strings. Apply the same validation to both schema occurrences so
generated clients reject unsupported values such as `star`.

In `@cmd/serve_contract.go`:
- Around line 39-42: Update statisticsOptions and its containing request
unmarshalling to reject explicit null for the statistics object, Enabled, and
Math fields instead of treating null as omitted/default; match the null-aware
unmarshalling behavior used by the conversion request types. Add or update tests
covering all three null cases while preserving valid omitted and non-null
values.

In `@cmd/serve_test.go`:
- Around line 145-159: Update the ServeSuite tests, including
TestCancellationShutsDownInMemoryListener and the additional affected test
cases, to replace direct receives from listener.acceptStarted and result with
the repository’s timeout-backed wait helper. Ensure every channel wait has a
bounded timeout while preserving the existing assertions and test behavior.

In `@cmd/serve.go`:
- Around line 143-155: Update the ctx.Done shutdown path in runServer so that
when shutdown returns an error, it calls server.Close(), drains and waits for
serveResult, then returns the shutdown error. Preserve the existing handling for
successful shutdown and update the shutdown-failure test to verify the serving
goroutine exits.

---

Nitpick comments:
In `@api/openapi_test.go`:
- Around line 109-130: Update TestDatasetSettingsRequireUniqueChartTypes to
verify duplicate settings for every supported chart type, rather than only bar.
Iterate over the complete supported chart-type set, construct two settings
entries with the current type, and assert validation returns the expected “want
at most 1” error for each iteration.

In `@pkg/core/core_test.go`:
- Around line 125-154: The TestConvertIdentifiesInapplicableOptions test
currently only checks that OptionError.Name is non-empty; update each test case
to carry and assert its exact expected name, including select, swap, and
grouping. Extend coverage with filter and jsonPath cases if supported by the
existing Convert error paths, while preserving the current errors.Is assertion.
🪄 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: 3dbfed2f-ab4d-4cfc-a42d-1d77caec903f

📥 Commits

Reviewing files that changed from the base of the PR and between 914ada1 and 8bcfa3a.

📒 Files selected for processing (17)
  • api/openapi.yaml
  • api/openapi_test.go
  • cmd/cli/dataflags.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
  • pkg/style/style.go
  • pkg/style/style_test.go
  • shared/chart_spec.go
  • shared/chart_spec_test.go

Comment thread api/openapi.yaml Outdated
Comment thread cmd/serve_contract.go
Comment thread cmd/serve_test.go Outdated
Comment thread cmd/serve.go
Comment thread api/openapi.yaml
@fahimfaisaal fahimfaisaal linked an issue Jul 20, 2026 that may be closed by this pull request
5 tasks
@fahimfaisaal

Copy link
Copy Markdown
Member

@hfl0506 you have some conflicts and request changes, please review. Thanks.

@hfl0506
hfl0506 force-pushed the feat/rest-root-endpoint branch from 08d64f2 to 88f1794 Compare July 20, 2026 14:55

@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: 1

🤖 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 `@cmd/serve_contract.go`:
- Around line 155-190: Update rejectNullFields and strictDecodeRequestObject to
preserve precise nested field paths for schema violations. Map
*json.UnmarshalTypeError paths using the relevant nullPath or prefix, and
convert non-object decoded values into bodyValidationError rather than allowing
root-level invalid_json errors. Ensure inputs such as grouping:"foo" and
grouping.pattern:123 identify the offending field path.
🪄 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: 072bfda2-8b96-4c04-98be-8fa5aea9a1a3

📥 Commits

Reviewing files that changed from the base of the PR and between a2d5e9c and 88f1794.

📒 Files selected for processing (13)
  • api/openapi.yaml
  • api/openapi_test.go
  • cmd/cli/dataflags.go
  • cmd/cli/flagbag_test.go
  • cmd/serve.go
  • cmd/serve_contract.go
  • cmd/serve_test.go
  • docs/src/content/docs/commands/serve.mdx
  • pkg/core/core.go
  • pkg/core/core_test.go
  • pkg/style/style.go
  • pkg/style/style_test.go
  • shared/chart_spec_test.go
🚧 Files skipped from review as they are similar to previous changes (11)
  • pkg/core/core_test.go
  • shared/chart_spec_test.go
  • pkg/style/style.go
  • cmd/cli/flagbag_test.go
  • pkg/core/core.go
  • api/openapi_test.go
  • cmd/serve.go
  • api/openapi.yaml
  • cmd/cli/dataflags.go
  • cmd/serve_test.go
  • pkg/style/style_test.go

Comment thread cmd/serve_contract.go
@hfl0506

hfl0506 commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

@hfl0506 you have some conflicts and request changes, please review. Thanks.

Hi, I resolved conflict, please take a look when you have a chance. Thanks

@fahimfaisaal fahimfaisaal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For the first version, LGTM. I found a bug btw

Image

we have two regions but it's showing 0.

Could you create a bug issue on it to fix it at next release

@fahimfaisaal
fahimfaisaal merged commit 564277a into goptics:main Jul 20, 2026
14 checks passed
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 root conversion through POST / [feat]: serve Vizb capabilities through a REST API

3 participants