Skip to content

feat(rest): add the Ossie REST API spec - #573

Open
jbonofre wants to merge 2 commits into
apache:mainfrom
jbonofre:feat/rest-api-spec
Open

jbonofre wants to merge 2 commits into
apache:mainfrom
jbonofre:feat/rest-api-spec

Conversation

@jbonofre

Copy link
Copy Markdown
Member

Summary

Follows the [PROPOSAL] Introduce Ossie REST API spec thread on dev@. Adds rest/openapi.yaml, a draft standard REST API to produce, consume and orchestrate Ossie models, meant to be implemented by catalogs (Polaris, Unity, Collibra, ...) and consumed by query engines, BI tools and converter orchestrators.

  • Media types: application/vnd.ossie+yaml / application/vnd.ossie+json, with an optional version="0.2" parameter as a negotiation hint (suggested by Sebastien Gandon). The document's version field stays authoritative.
  • Models: GET/POST /v1/models, GET/PUT/DELETE /v1/models/{name}, with ETag/If-Match and page-token pagination. The document schema references core-spec/ossie-schema.json.
  • Converters: GET /v1/converters, POST /v1/converters/{converter}/import and /export.
  • Validation / discovery: POST /v1/validate, GET /v1/config.
  • Errors, auth, extensions: RFC 9457 problem+json, bearer auth, vendor extensions under /v1/x-{vendor}/.

Open points

  • Alignment with the Polaris Ossie REST API design doc (models are in a flat namespace here, no catalog/namespace hierarchy).
  • Conversion warnings: warnings field on import, X-Ossie-Warning headers on export.
  • Docs (rest/README.md) and a CI lint step for the spec are not included yet.

Test plan

  • openapi.yaml validates as OpenAPI 3.1 with openapi-spec-validator, including the core-spec/ossie-schema.json reference.

Define a standard REST API to produce, consume and orchestrate Ossie
models: model CRUD with ETag concurrency, validation, and converter
import/export. Documents are exchanged as application/vnd.ossie+yaml or
application/vnd.ossie+json with an optional spec-line version parameter.

@ayushtkn ayushtkn 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.

Thanx @jbonofre, dropped few suggestions, rest LGTM

Comment thread rest/openapi.yaml
"200":
description: The converted model.
content:
application/vnd.ossie+yaml: { schema: { $ref: "#/components/schemas/ConversionResult" } }

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.

/import says the result is an Ossie document, then types vnd.ossie+yaml and vnd.ossie+json as ConversionResult ({model, warnings}). Those media types are defined at the top of this file as a core-spec document, and that schema's root has additionalProperties: false, so a client validating vnd.ossie against ossie-schema.json rejects the wrapper outright:

'version' is a required property
'name' is a required property
'datasets' is a required property
Additional properties are not allowed ('model', 'warnings' were unexpected)

Return the bare document as vnd.ossie with warnings in headers, as export does, or keep ConversionResult on application/json only.

Comment thread rest/openapi.yaml
Either send an Ossie document as the body, or send `{"model": "<name>"}`
as `application/json` to export a stored model. Conversion warnings
(constructs the target cannot express) are returned in the
`Ossie-Warnings` header as a count and in `X-Ossie-Warning` headers.

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.

The description cites Ossie-Warnings as a count and repeated X-Ossie-Warning headers; the PR text mentions only X-Ossie-Warning. The 200 response at :278 declares neither, so generated clients will drop them. Declare one header shape in the response object — repeated values are awkward to express in OpenAPI, so a single X-Ossie-Warning holding a list, or a JSON body, is easier to implement the same way twice. X- prefixes are also deprecated by RFC 6648.

Comment thread rest/openapi.yaml
schema: { type: string }

parameters:
ModelName:

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.

The path segment and the document's required name can diverge, and nothing says what happens: PUT /v1/models/orders carrying name: customers could rename, 400, or 409. The parameter has no description, and Conflict at line 427 reads "A model with this name already exists" without saying which name it means.

A mismatch needs a declared outcome — 400 on create and replace — so that two catalogs do not pick differently.

Comment thread rest/openapi.yaml
nothing is stored unless `store` is true.
parameters:
- $ref: "#/components/parameters/ConverterName"
- name: store

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.

store=true is a second operation with no contract. Import declares ['200','401','404','415','422'] — no 201, no Location, no ETag, and no 409 when that name already exists.
A caller cannot distinguish "converted" from "stored", or "stored" from "stored over an existing model".

Comment thread rest/openapi.yaml
description: A semantic model, as defined by the Ossie core specification.
$ref: "../core-spec/ossie-schema.json"

ModelSummary:

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.

Two of the three mechanisms you offered Jakub on the thread are not here. You wrote "1. Explicit model versioning (e.g. version_id, updated_at, content hash) … 3. Lightweight HEAD / summary metadata on endpoints so catalogs can poll cheaply without fetching the full model." Conditional requests (2) are implemented — If-None-Match/304 and If-Match/412 — but there is no HEAD on any path, and ModelSummary is [name, description, version, etag] with no updated_at or version_id. version here is documented as the Ossie spec version, not a model revision, which is the confusion a polling catalog will hit first.

Comment thread rest/openapi.yaml
responses:
BadRequest: { description: "Malformed request.", content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } } }
Unauthorized: { description: "Missing or invalid credentials.", content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } } }
NotFound: { description: "Resource not found.", content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } } }

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.

404 is declared on both /import and /export, where it can mean an unknown converter or, for {"model": "<name>"} on export, an unknown model. Those need to be distinguishable by a client — either separate responses, or a machine-readable field naming the missing resource.

Comment thread rest/openapi.yaml
type: array
items: { type: string }
examples: [["application/vnd.ossie+yaml", "application/vnd.ossie+json"]]
features:

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.

A closed enum of models | validate | conversions means a server that adds a feature returns a value this schema forbids. Many generated clients reject the whole payload on an unknown enum value rather than the one field, so an older client breaks against a newer server. For a spec meant to be implemented by several catalogs independently, this should be an open string.

Comment thread rest/openapi.yaml
BadRequest: { description: "Malformed request.", content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } } }
Unauthorized: { description: "Missing or invalid credentials.", content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } } }
NotFound: { description: "Resource not found.", content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } } }
NotAcceptable: { description: "No representation matches Accept, including the version parameter.", content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } } }

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.

406 is declared only on GET /v1/models/{name}. Create and replace have no response for a body whose version names a spec line the server does not implement — today it falls through to a generic 422, which a client cannot distinguish from a malformed model. They need a 406 of their own, with ServerConfig.spec_versions as the advertised set.

Comment thread rest/openapi.yaml Outdated
schemas:
OssieDocument:
description: A semantic model, as defined by the Ossie core specification.
$ref: "../core-spec/ossie-schema.json"

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.

This resolves only when the file is read beside core-spec/. Served alone — the normal case for a catalog publishing its API — it cannot be resolved at all, so Swagger UI and codegen from a URL fail. The reference needs a form a consumer holding only this document can resolve: an absolute $id-based reference or a bundled copy. Note the trade-off either way — a relative ref tracks the repo revision, whereas the raw .../main/... URL that ontology/ontology.json uses floats with main.

Comment thread rest/openapi.yaml
type: array
items: { type: string, enum: [models, validate, conversions] }

ValidationResult:

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.

{valid, errors} has nowhere for warnings, but validation/validate.py:114 defines ValidationWarning and reports them separately from errors. A server wrapping the reference validator must either discard them or misreport them, and a document with warnings only would come back valid: true, errors: [].

@dimas-b dimas-b 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.

Posting some comments from the potential implementor's POV (Polaris). I hope you find them reasonable.

Comment thread rest/openapi.yaml
## Extensibility

Servers MAY add vendor-specific response fields and operations under
`/v1/x-{vendor}/`. Clients MUST ignore response fields they do not know.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since it's a custom path, does it have to be specified at Ossie? Any custom path should work fine as long as it does not overlap with Ossie paths.

Also v1 is probably not relevant to custom paths as they will have to follow their own versioning.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

indeed, why v1 ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That's fair as soon as it's "delegated" to the vendor. I will update accordingly.

Comment thread rest/openapi.yaml
- $ref: "#/components/parameters/PageToken"
- name: name
in: query
description: Return only models whose name contains this text.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If this is meant to be a query rather than exact name, would name-contains be clearer?

Otherwise, GET /v1/models?name=foo returning foobar looks strange 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point, let me clarify this one.

Comment thread rest/openapi.yaml
application/json:
schema:
$ref: "#/components/schemas/ListModelsResponse"
"401": { $ref: "#/components/responses/Unauthorized" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should /config have a 401 response too?

What about 403?

Comment thread rest/openapi.yaml
Location: { schema: { type: string, format: uri-reference } }
content:
application/vnd.ossie+yaml: { schema: { $ref: "#/components/schemas/OssieDocument" } }
application/vnd.ossie+json: { schema: { $ref: "#/components/schemas/OssieDocument" } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Would it make sense to add a note that clients SHOULD provide the Accept header?

Comment thread rest/openapi.yaml
* `application/vnd.ossie+json`

Both accept an optional `version` parameter naming a spec line, for example
`application/vnd.ossie+yaml; version="0.2"` meaning any `0.2.x` document. The

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should version be a query parameter perhaps? It looks like it's a hard expectation of the client rather than a content "negotiation" option 🤔 A server may also only support one particular format version... Is real negotiation possible?

Comment thread rest/openapi.yaml
get:
tags: [Conversions]
operationId: listConverters
summary: List the converters registered on this server

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Would it make sense to define pagination from the start? It is usually very hard to add later (due to old clients).

Comment thread rest/openapi.yaml
description: ETag the client last saw.
schema: { type: string }
PageSize:
name: page_size

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WDYT about stating that page size is only a hint to the server and servers can return less entries than page_size?.. e.g. total content size may be a factor.

Comment thread rest/openapi.yaml
media_types:
type: array
items: { type: string }
examples: [["application/vnd.ossie+yaml", "application/vnd.ossie+json"]]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In the top-level description, version is part of the media type, so having it separate here contradicts that. Note: from my POV, handling spec version separately from media types may be preferable.

Comment thread rest/openapi.yaml
description: Server capabilities.

paths:
/v1/config:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In my experience it is valuable to make the config endpoint stable as the API evolves. This way clients can discover the highest API version supported by the server via a well-known config URL.

In other words, WDYT about /config (without v1)?

If newer API versions return different configuration JSON shapes, they can add /v2/config for the extra information.

Comment thread rest/openapi.yaml
type: object
required: [spec_versions, media_types]
properties:
spec_versions:

@dimas-b dimas-b Oct 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It could be valuable to also list API versions (of this API) supported by the server.

It may be that a v2 is required sooner than all clients migrate, so server may want to support multiple APIs.

Checking /vN/config may not be conclusive because a 404 response may be due to a client configuration mistake (bad base URL) rather than lack of server capabilities. Conversely, a 200 response may be due to hitting an config endpoint of another API (again due to badly configured URI).

Additionally, the version info may include data about customizations (extra paths) supported by the server.

Comment thread rest/openapi.yaml
Comment on lines +378 to +386
valid: { type: boolean }
errors:
type: array
items:
type: object
required: [path, message]
properties:
path: { type: string, description: JSON Pointer into the document. }
message: { type: string }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With custom dialects / extensions, I think we'll see a fair number of documents that pass ossie validate but that a client still can't fully parse unless it knows the vendor extension. #576 hints at this even at the smallest scale (a single-SUM metric comes back in a vendor-only dialect from several converters) so "valid" and "readable by the next tool" aren't quite the same thing yet.

Building on the warnings idea above, I wonder if the response could also carry information about the parts an implementation didn't validate or doesn't recognize. A caller compiling queries could use that information to understand how likely they are to succeed, rather than discovering it the hard way. Validating specific vendor extensions could stay optional and up to each implementer.

@ekut, you've been doing a lot of the query-experience thinking here, so I'd really value your opinion on what kind of metadata would be useful to you when dealing with models that have lots of vendor dialects / extensions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the ping. Two things, both as warnings in the shape errors already has, plus a code:

{"path": "/metrics/0/expression", "code": "no_core_dialect", "message": "only THOUGHTSPOT"}
{"path": "/metrics/0/expression/dialects/0", "code": "not_checked", "message": "no parser for THOUGHTSPOT"}

no_core_dialect: a field or metric with no ANSI_SQL or OSSIE_SQL_2026 entry. A client can work this out itself, but it is the first line a compiler draws (ossie-clickhouse refuses such a document), and a shared code puts that line in the same place for every tool.

not_checked: a dialect entry, or a custom_extensions vendor, the server did not validate. Only the server knows this. On current main, validate.py passes a metric whose only entry is THOUGHTSPOT: "this is ((( not a formula" with no warning, since THOUGHTSPOT and six other dialects are in SKIP_SQL_VALIDATION; the same text tagged ANSI_SQL fails. Today a caller cannot tell "checked and fine" from "not looked at".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ekut, that shape covers what I had in mind. @jbonofre, would a warnings array next to errors (same path and message, plus a code) work for ValidationResult, with ConversionResult.warnings using the same shape instead of plain strings? That would also answer @ayushtkn's point, and here or in a follow-up is your call.

Comment thread rest/openapi.yaml
Comment on lines +49 to +51
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These seem redundant. Can we remove them?

Comment thread rest/openapi.yaml Outdated
schemas:
OssieDocument:
description: A semantic model, as defined by the Ossie core specification.
$ref: "../core-spec/ossie-schema.json"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

core-spec/ossie-schema.json currently defines source as a dot-delimited string, IIRC.

This may be a problem in catalogs (like Polaris) that permit dots inside table names.

@jbonofre @ayushtkn: Do you think this is worth a dev discussion?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agreed with @ayushtkn , referring like ../core-spec/ossie-schema.json is problematic in openAPI.

Moreover, I think the Ossie core spec schema should be versioned.

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.

+1 for a dev@ thread.

The gap is wider than the dots: source is only {"type": "string", "minLength": 1} with no
pattern and no delimiter rule. The dotted form lives in prose and examples, not in the schema,
and nothing in the repo parses it. So a.b.c could be a three-part name or a two-part one whose
table is b.c, with no escaping rule to tell them apart. Iceberg REST sidesteps this by
delimiting namespaces with %1F rather than a dot, so any catalog on that model has to guess
when it reads an Ossie source.

One thing that may let the two proceed in parallel: OssieDocument is a bare $ref and doesn't
restate source, so whatever dev@ settles should land here without needing a change to this
spec. #298, #173, #338 and #104 are all open on this, and #637 was closed on 25 Oct after
reviewers asked to take source portability to dev@

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The reason I did like this is because we don't have a release yet (so nothing publicly available as an URL).
I mimic what we have in the validate.py but I forgot this one (I did for ontology).

Let me use raw.githubusercontent.com/apache/ossie/main/... as for the other.

I will start a discussion about source on the dev mailing list.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, that's exactly the Polaris case: apache/polaris#5685

Iceberg table names can have a . in them, but Dataset.source is still just a plain string. The dotted form is only mentioned in the description, with no quoting. That PR tried to guess the split from catalog state, which isn't a great long-term model, so it's on hold for now...

A typical Hive-style sales.public.orders still binds fine, with the last segment as the table. The clash only shows up when the table name itself has a dot. For example ns=a table=b.c vs ns=a.b table=c both end up as the same string a.b.c.

cc: @jbonofre @dimas-b

@dimas-b dimas-b Oct 29, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thank you for leading the discussion, @jbonofre !

IMHO, since source is defined by Ossie, parsing rules should also be defined by Ossie to ensure interoperability. An Ossie document should be parseable the same way in all catalog implementations, I think.

The relative ../core-spec/ossie-schema.json reference only resolves when
the spec is read beside core-spec/, so Swagger UI and codegen fail on a
published copy. Point OssieDocument at the raw GitHub URL, as the
ontology schemas already do.

@christianeu-db christianeu-db left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @jbonofre! Left one note on the schema reference and a reply on the validation thread.

Comment thread rest/openapi.yaml
type: array
items: { type: string }
description: Ossie spec versions the server can read and write.
examples: [["0.2.0.dev0"]]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since #691 the schema on main requires version: 0.3.0.dev0, so a body that uses the spec_versions example (0.2.0.dev0) now fails OssieDocument with [Schema] version: '0.3.0.dev0' was expected. The version="0.2" media-type example at the top still says 0.2 too. Could both move to 0.3?

Comment thread rest/openapi.yaml
Comment on lines +378 to +386
valid: { type: boolean }
errors:
type: array
items:
type: object
required: [path, message]
properties:
path: { type: string, description: JSON Pointer into the document. }
message: { type: string }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ekut, that shape covers what I had in mind. @jbonofre, would a warnings array next to errors (same path and message, plus a code) work for ValidationResult, with ConversionResult.warnings using the same shape instead of plain strings? That would also answer @ayushtkn's point, and here or in a follow-up is your call.

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.

8 participants