This is an automated email from the ASF dual-hosted git repository.

jerryshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 27eed7ecba [#12609] docs(open-api): Add Semantic Model REST API 
(#12627)
27eed7ecba is described below

commit 27eed7ecbaa97bf27242cb42ec29f50dad8028ca
Author: mchades <[email protected]>
AuthorDate: Mon Oct 12 10:46:07 2026 +0800

    [#12609] docs(open-api): Add Semantic Model REST API (#12627)
    
    ### What changes were proposed in this pull request?
    
    Document the Semantic Model list, create, load, alter, and drop APIs and
    Apache Ossie import/export, including schemas, media types, errors, and
    examples.
    
    Native REST definitions use camelCase fields; standalone Ossie documents
    use snake_case fields and string source identifiers. The specification
    also covers the reserved properties extension, document version, export
    format, and download headers.
    
    Adds an HTTP regression test confirming that an omitted `Content-Type`
    is accepted and parsed as YAML.
    
    ### Why are the changes needed?
    
    Provide the OpenAPI contract for the Semantic Model REST APIs now
    implemented on main.
    
    Fix: #12609
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. It adds API documentation only.
    
    ### How was this patch tested?
    
    - `./gradlew :docs:spotlessApply :docs:build`
    - `./gradlew :server:spotlessApply :server:test --tests
    org.apache.gravitino.server.web.rest.TestSemanticModelOperations
    -PskipITs -PskipDockerTests=true` — 26 REST tests passed; Docker tests
    were not run.
    - `git diff --check`
    - Compared schema fields with the native DTOs and checked 15 schema
    examples and request boundaries, including field casing and omitted/null
    properties.
---
 docs/open-api/openapi.yaml                         |   20 +
 docs/open-api/semantic-models.yaml                 | 1591 ++++++++++++++++++++
 .../web/rest/TestSemanticModelOperations.java      |   53 +
 3 files changed, 1664 insertions(+)

diff --git a/docs/open-api/openapi.yaml b/docs/open-api/openapi.yaml
index 59bfdebe26..fb426a9a8d 100644
--- a/docs/open-api/openapi.yaml
+++ b/docs/open-api/openapi.yaml
@@ -168,6 +168,18 @@ paths:
   /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views/{view}:
     $ref: 
"./views.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1views~1%7Bview%7D"
 
+  /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/semantic-models:
+    $ref: 
"./semantic-models.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1semantic-models"
+
+  
/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/semantic-models/ossie:
+    $ref: 
"./semantic-models.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1semantic-models~1ossie"
+
+  
/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/semantic-models/{semanticModel}:
+    $ref: 
"./semantic-models.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1semantic-models~1%7BsemanticModel%7D"
+
+  
/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/semantic-models/{semanticModel}/ossie:
+    $ref: 
"./semantic-models.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1semantic-models~1%7BsemanticModel%7D~1ossie"
+
   /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/functions:
     $ref: 
"./functions.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1functions"
 
@@ -569,6 +581,14 @@ components:
       schema:
         type: string
 
+    semanticModel:
+      name: semanticModel
+      in: path
+      description: The name of the Semantic Model
+      required: true
+      schema:
+        type: string
+
     model:
       name: model
       in: path
diff --git a/docs/open-api/semantic-models.yaml 
b/docs/open-api/semantic-models.yaml
new file mode 100644
index 0000000000..d81640988c
--- /dev/null
+++ b/docs/open-api/semantic-models.yaml
@@ -0,0 +1,1591 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+---
+
+paths:
+
+  /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/semantic-models:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/metalake"
+      - $ref: "./openapi.yaml#/components/parameters/catalog"
+      - $ref: "./openapi.yaml#/components/parameters/schema"
+
+    get:
+      tags:
+        - semantic-model
+      summary: List Semantic Models
+      description: >-
+        Lists the Semantic Models in a schema. Semantic Models are supported 
only for relational
+        catalogs. Each returned identifier uses the full 
metalake.catalog.schema.name namespace.
+      operationId: listSemanticModels
+      responses:
+        "200":
+          $ref: "#/components/responses/SemanticModelListResponse"
+        "400":
+          $ref: "#/components/responses/SemanticModelBadRequestResponse"
+        "403":
+          $ref: "#/components/responses/SemanticModelForbiddenResponse"
+        "404":
+          $ref: "#/components/responses/SemanticModelParentNotFoundResponse"
+        "501":
+          $ref: 
"#/components/responses/SemanticModelUnsupportedCatalogResponse"
+        "5XX":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    post:
+      tags:
+        - semantic-model
+      summary: Create a Semantic Model
+      description: >-
+        Creates a schema-scoped Semantic Model in a relational catalog. The 
complete definition is
+        validated before the model is stored, including source identifier 
shape. Source existence,
+        source columns, and source authorization checks are not yet 
implemented.
+      operationId: createSemanticModel
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/SemanticModelCreateRequest"
+            examples:
+              SemanticModelCreateRequest:
+                $ref: "#/components/examples/SemanticModelCreateRequest"
+      responses:
+        "200":
+          $ref: "#/components/responses/SemanticModelResponse"
+        "400":
+          $ref: "#/components/responses/SemanticModelBadRequestResponse"
+        "403":
+          $ref: "#/components/responses/SemanticModelForbiddenResponse"
+        "404":
+          $ref: "#/components/responses/SemanticModelParentNotFoundResponse"
+        "409":
+          $ref: "#/components/responses/SemanticModelAlreadyExistsResponse"
+        "501":
+          $ref: 
"#/components/responses/SemanticModelUnsupportedCatalogResponse"
+        "502":
+          $ref: "#/components/responses/SemanticModelConnectionFailedResponse"
+        "5XX":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  
/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/semantic-models/ossie:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/metalake"
+      - $ref: "./openapi.yaml#/components/parameters/catalog"
+      - $ref: "./openapi.yaml#/components/parameters/schema"
+
+    post:
+      tags:
+        - semantic-model
+      summary: Import an Apache Ossie Semantic Model
+      description: >-
+        Imports one standalone Apache Ossie document and creates the 
represented Semantic Model.
+        The request body is the raw JSON or YAML document, not a JSON-encoded 
string. Content-Type
+        selects the parser; when the header is absent, the body is parsed as 
YAML. Parsing does not
+        fall back to another format.
+      operationId: importOssieDocument
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/OssieSemanticModelDocument"
+            examples:
+              OssieSemanticModelDocument:
+                $ref: "#/components/examples/OssieSemanticModelDocument"
+          application/yaml:
+            schema:
+              $ref: "#/components/schemas/OssieSemanticModelDocument"
+            examples:
+              OssieSemanticModelDocument:
+                $ref: "#/components/examples/OssieSemanticModelDocument"
+          application/x-yaml:
+            schema:
+              $ref: "#/components/schemas/OssieSemanticModelDocument"
+            examples:
+              OssieSemanticModelDocument:
+                $ref: "#/components/examples/OssieSemanticModelDocument"
+          text/yaml:
+            schema:
+              $ref: "#/components/schemas/OssieSemanticModelDocument"
+            examples:
+              OssieSemanticModelDocument:
+                $ref: "#/components/examples/OssieSemanticModelDocument"
+      responses:
+        "200":
+          $ref: "#/components/responses/SemanticModelResponse"
+        "400":
+          $ref: "#/components/responses/SemanticModelBadRequestResponse"
+        "403":
+          $ref: "#/components/responses/SemanticModelForbiddenResponse"
+        "404":
+          $ref: "#/components/responses/SemanticModelParentNotFoundResponse"
+        "409":
+          $ref: "#/components/responses/SemanticModelAlreadyExistsResponse"
+        "415":
+          description: >-
+            Unsupported Media Type - An explicitly supplied Content-Type is 
not one of
+            application/json, application/yaml, application/x-yaml, or 
text/yaml
+        "501":
+          $ref: 
"#/components/responses/SemanticModelUnsupportedCatalogResponse"
+        "502":
+          $ref: "#/components/responses/SemanticModelConnectionFailedResponse"
+        "5XX":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  
/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/semantic-models/{semanticModel}:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/metalake"
+      - $ref: "./openapi.yaml#/components/parameters/catalog"
+      - $ref: "./openapi.yaml#/components/parameters/schema"
+      - $ref: "./openapi.yaml#/components/parameters/semanticModel"
+
+    get:
+      tags:
+        - semantic-model
+      summary: Load a Semantic Model
+      description: Returns the specified Semantic Model with its nested 
definition and audit data.
+      operationId: loadSemanticModel
+      responses:
+        "200":
+          $ref: "#/components/responses/SemanticModelResponse"
+        "400":
+          $ref: "#/components/responses/SemanticModelBadRequestResponse"
+        "403":
+          $ref: "#/components/responses/SemanticModelForbiddenResponse"
+        "404":
+          $ref: "#/components/responses/SemanticModelNotFoundResponse"
+        "501":
+          $ref: 
"#/components/responses/SemanticModelUnsupportedCatalogResponse"
+        "5XX":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    put:
+      tags:
+        - semantic-model
+      summary: Alter a Semantic Model
+      description: >-
+        Applies the ordered updates atomically. The server validates and 
converts the complete
+        update batch before dispatch; if any update or the resulting model is 
invalid, none of the
+        updates is applied. Only replaceDefinition revalidates the complete 
definition. Source
+        existence, source columns, and source authorization checks are not yet 
implemented.
+      operationId: alterSemanticModel
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/SemanticModelUpdatesRequest"
+            examples:
+              SemanticModelUpdatesRequest:
+                $ref: "#/components/examples/SemanticModelUpdatesRequest"
+      responses:
+        "200":
+          $ref: "#/components/responses/SemanticModelResponse"
+        "400":
+          $ref: "#/components/responses/SemanticModelBadRequestResponse"
+        "403":
+          $ref: "#/components/responses/SemanticModelForbiddenResponse"
+        "404":
+          $ref: "#/components/responses/SemanticModelNotFoundResponse"
+        "409":
+          $ref: "#/components/responses/SemanticModelAlterConflictResponse"
+        "501":
+          $ref: 
"#/components/responses/SemanticModelUnsupportedCatalogResponse"
+        "502":
+          $ref: "#/components/responses/SemanticModelConnectionFailedResponse"
+        "5XX":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    delete:
+      tags:
+        - semantic-model
+      summary: Drop a Semantic Model
+      description: >-
+        Drops the specified Semantic Model. A missing schema or Semantic Model 
is reported as a
+        successful response with dropped set to false.
+      operationId: dropSemanticModel
+      responses:
+        "200":
+          $ref: "./openapi.yaml#/components/responses/DropResponse"
+        "400":
+          $ref: "#/components/responses/SemanticModelBadRequestResponse"
+        "403":
+          $ref: "#/components/responses/SemanticModelForbiddenResponse"
+        "404":
+          description: Not Found - The target metalake or catalog does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchMetalakeException:
+                  $ref: 
"./metalakes.yaml#/components/examples/NoSuchMetalakeException"
+                NoSuchCatalogException:
+                  $ref: 
"./catalogs.yaml#/components/examples/NoSuchCatalogException"
+        "409":
+          $ref: "#/components/responses/SemanticModelOptimisticLockResponse"
+        "501":
+          $ref: 
"#/components/responses/SemanticModelUnsupportedCatalogResponse"
+        "5XX":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  
/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/semantic-models/{semanticModel}/ossie:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/metalake"
+      - $ref: "./openapi.yaml#/components/parameters/catalog"
+      - $ref: "./openapi.yaml#/components/parameters/schema"
+      - $ref: "./openapi.yaml#/components/parameters/semanticModel"
+
+    get:
+      tags:
+        - semantic-model
+      summary: Export a Semantic Model as an Apache Ossie document
+      description: >-
+        Exports the specified Semantic Model as a standalone Apache Ossie YAML 
or JSON document.
+        The response is downloadable document content rather than the native 
Gravitino response
+        envelope. The format query parameter selects the representation 
independently of the
+        Accept header.
+      operationId: exportOssieDocument
+      parameters:
+        - name: format
+          in: query
+          description: The serialized document format
+          required: false
+          schema:
+            type: string
+            enum:
+              - yaml
+              - json
+            default: yaml
+      responses:
+        "200":
+          $ref: "#/components/responses/OssieSemanticModelDocumentResponse"
+        "400":
+          $ref: "#/components/responses/SemanticModelBadRequestResponse"
+        "403":
+          $ref: "#/components/responses/SemanticModelForbiddenResponse"
+        "404":
+          $ref: "#/components/responses/SemanticModelNotFoundResponse"
+        "501":
+          $ref: 
"#/components/responses/SemanticModelUnsupportedCatalogResponse"
+        "5XX":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+components:
+
+  schemas:
+    AIContext:
+      description: Additional context for AI tools, encoded as exactly one 
string or object value
+      oneOf:
+        - type: string
+        - $ref: "#/components/schemas/AIContextObject"
+
+    AIContextObject:
+      type: object
+      description: >-
+        Structured context for AI tools. Unknown properties may contain any 
JSON value and are
+        retained losslessly in input order.
+      properties:
+        instructions:
+          type: string
+          description: Instructions for AI tools on how to use the entity
+        synonyms:
+          type: array
+          description: >-
+            Alternative names and terms. Omitting the property differs from 
supplying an empty
+            array.
+          items:
+            type: string
+        examples:
+          type: array
+          description: >-
+            Sample questions or use cases. Omitting the property differs from 
supplying an empty
+            array.
+          items:
+            type: string
+      additionalProperties: true
+
+    SemanticModelSource:
+      type: object
+      description: >-
+        A governed Table or logical View expressed as catalog.schema.name. The 
endpoint metalake is
+        inherited and therefore is not included in this identifier.
+      required:
+        - namespace
+        - name
+      properties:
+        namespace:
+          type: array
+          minItems: 2
+          maxItems: 2
+          description: Exactly the catalog and schema containing the source, 
in that order
+          items:
+            type: string
+            minLength: 1
+        name:
+          type: string
+          minLength: 1
+          description: The source Table or logical View name
+      additionalProperties: false
+
+    Dataset:
+      type: object
+      description: A logical dataset backed by a governed Gravitino Table or 
logical View
+      required:
+        - name
+        - source
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The dataset name, unique within the Semantic Model
+        source:
+          $ref: "#/components/schemas/SemanticModelSource"
+        primaryKey:
+          type: array
+          description: >-
+            Source columns that form the primary key. The property may be 
omitted; an explicitly
+            supplied empty array is retained as empty.
+          items:
+            type: string
+            minLength: 1
+        uniqueKeys:
+          type: array
+          description: >-
+            Source column sets that form unique keys. The property may be 
omitted; an explicitly
+            supplied empty array is retained as empty.
+          items:
+            type: array
+            minItems: 1
+            items:
+              type: string
+              minLength: 1
+        description:
+          type: string
+          description: The human-readable dataset description
+        aiContext:
+          $ref: "#/components/schemas/AIContext"
+        fields:
+          type: array
+          description: >-
+            Ordered fields in the dataset. The property may be omitted; an 
explicitly supplied
+            empty array is retained as empty.
+          items:
+            $ref: "#/components/schemas/Field"
+        customExtensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. The property may be omitted; an 
explicitly supplied empty
+            array is retained as empty.
+          items:
+            $ref: "#/components/schemas/CustomExtension"
+      additionalProperties: false
+
+    OssieDataset:
+      type: object
+      description: >-
+        An Apache Ossie dataset backed by a governed Gravitino Table or 
logical View
+      required:
+        - name
+        - source
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The dataset name, unique within the Semantic Model
+        source:
+          type: string
+          minLength: 1
+          description: >-
+            A catalog.schema.name source identifier. Segments containing dots 
or backticks use
+            backtick quoting, and a backtick inside a quoted segment is 
doubled. Query sources are
+            not supported.
+        primary_key:
+          type: array
+          description: >-
+            Source columns that form the primary key. The property may be 
omitted; an explicitly
+            supplied empty array is retained as empty.
+          items:
+            type: string
+            minLength: 1
+        unique_keys:
+          type: array
+          description: >-
+            Source column sets that form unique keys. The property may be 
omitted; an explicitly
+            supplied empty array is retained as empty.
+          items:
+            type: array
+            minItems: 1
+            items:
+              type: string
+              minLength: 1
+        description:
+          type: string
+          description: The human-readable dataset description
+        ai_context:
+          $ref: "#/components/schemas/AIContext"
+        fields:
+          type: array
+          description: >-
+            Ordered fields in the dataset. The property may be omitted; an 
explicitly supplied
+            empty array is retained as empty.
+          items:
+            $ref: "#/components/schemas/OssieField"
+        custom_extensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. The property may be omitted; an 
explicitly supplied empty
+            array is retained as empty.
+          items:
+            $ref: "#/components/schemas/OssieCustomExtension"
+      additionalProperties: false
+
+    Field:
+      type: object
+      description: A row-level semantic attribute in a dataset
+      required:
+        - name
+        - expression
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The field name, unique within its dataset
+        expression:
+          $ref: "#/components/schemas/Expression"
+        dimension:
+          $ref: "#/components/schemas/Dimension"
+        label:
+          type: string
+          description: The display label used to categorize the field
+        description:
+          type: string
+          description: The human-readable field description
+        datatype:
+          $ref: "#/components/schemas/SemanticModelDataType"
+        aiContext:
+          $ref: "#/components/schemas/AIContext"
+        customExtensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. The property may be omitted; an 
explicitly supplied empty
+            array is retained as empty.
+          items:
+            $ref: "#/components/schemas/CustomExtension"
+      additionalProperties: false
+
+    Relationship:
+      type: object
+      description: A relationship between two datasets in the same Semantic 
Model
+      required:
+        - name
+        - from
+        - to
+        - fromColumns
+        - toColumns
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The relationship name, unique within the Semantic Model
+        from:
+          type: string
+          minLength: 1
+          description: The source dataset name
+        to:
+          type: string
+          minLength: 1
+          description: The target dataset name
+        fromColumns:
+          type: array
+          minItems: 1
+          description: >-
+            Non-empty source columns. The array must have the same number of 
entries as toColumns.
+          items:
+            type: string
+            minLength: 1
+        toColumns:
+          type: array
+          minItems: 1
+          description: >-
+            Non-empty target columns. The array must have the same number of 
entries as
+            fromColumns.
+          items:
+            type: string
+            minLength: 1
+        aiContext:
+          $ref: "#/components/schemas/AIContext"
+        customExtensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. The property may be omitted; an 
explicitly supplied empty
+            array is retained as empty.
+          items:
+            $ref: "#/components/schemas/CustomExtension"
+      additionalProperties: false
+
+    Metric:
+      type: object
+      description: A model-scoped quantitative measure
+      required:
+        - name
+        - expression
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The metric name, unique within the Semantic Model
+        expression:
+          $ref: "#/components/schemas/Expression"
+        description:
+          type: string
+          description: The human-readable metric description
+        datatype:
+          $ref: "#/components/schemas/SemanticModelDataType"
+        aiContext:
+          $ref: "#/components/schemas/AIContext"
+        customExtensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. The property may be omitted; an 
explicitly supplied empty
+            array is retained as empty.
+          items:
+            $ref: "#/components/schemas/CustomExtension"
+      additionalProperties: false
+
+    Expression:
+      type: object
+      description: An expression with one or more ordered dialect-specific 
representations
+      required:
+        - dialects
+      properties:
+        dialects:
+          type: array
+          minItems: 1
+          description: Each dialect identifier may occur at most once
+          items:
+            $ref: "#/components/schemas/DialectExpression"
+      additionalProperties: false
+
+    DialectExpression:
+      type: object
+      description: An expression written in a specific dialect
+      required:
+        - dialect
+        - expression
+      properties:
+        dialect:
+          type: string
+          minLength: 1
+          description: >-
+            An open dialect identifier. ANSI_SQL, SNOWFLAKE, MDX, TABLEAU, 
DATABRICKS, MAQL, and
+            BIGQUERY are well-known Apache Ossie values; other non-empty 
identifiers are accepted
+            and preserved without conversion or fallback.
+        expression:
+          type: string
+          minLength: 1
+          description: The non-empty dialect-specific expression text
+      additionalProperties: false
+
+    Dimension:
+      type: object
+      description: >-
+        Dimension metadata for a field. An empty object is valid and remains 
distinct from an
+        omitted dimension.
+      properties:
+        isTime:
+          type: boolean
+          nullable: true
+          description: Whether consumers should treat the field as a time 
dimension
+      additionalProperties: false
+
+    CustomExtension:
+      type: object
+      description: Vendor-specific attributes retained for Semantic Model 
interchange
+      required:
+        - vendorName
+        - data
+      properties:
+        vendorName:
+          type: string
+          description: The extension vendor name
+        data:
+          type: string
+          description: Opaque vendor-specific data retained as a string
+      additionalProperties: false
+
+    OssieField:
+      type: object
+      description: A row-level semantic attribute in a dataset
+      required:
+        - name
+        - expression
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The field name, unique within its dataset
+        expression:
+          $ref: "#/components/schemas/Expression"
+        dimension:
+          $ref: "#/components/schemas/OssieDimension"
+        label:
+          type: string
+          description: The display label used to categorize the field
+        description:
+          type: string
+          description: The human-readable field description
+        datatype:
+          $ref: "#/components/schemas/SemanticModelDataType"
+        ai_context:
+          $ref: "#/components/schemas/AIContext"
+        custom_extensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. The property may be omitted; an 
explicitly supplied empty
+            array is retained as empty.
+          items:
+            $ref: "#/components/schemas/OssieCustomExtension"
+      additionalProperties: false
+
+    OssieRelationship:
+      type: object
+      description: A relationship between two datasets in the same Semantic 
Model
+      required:
+        - name
+        - from
+        - to
+        - from_columns
+        - to_columns
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The relationship name, unique within the Semantic Model
+        from:
+          type: string
+          minLength: 1
+          description: The source dataset name
+        to:
+          type: string
+          minLength: 1
+          description: The target dataset name
+        from_columns:
+          type: array
+          minItems: 1
+          description: >-
+            Non-empty source columns. The array must have the same number of 
entries as to_columns.
+          items:
+            type: string
+            minLength: 1
+        to_columns:
+          type: array
+          minItems: 1
+          description: >-
+            Non-empty target columns. The array must have the same number of 
entries as
+            from_columns.
+          items:
+            type: string
+            minLength: 1
+        ai_context:
+          $ref: "#/components/schemas/AIContext"
+        custom_extensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. The property may be omitted; an 
explicitly supplied empty
+            array is retained as empty.
+          items:
+            $ref: "#/components/schemas/OssieCustomExtension"
+      additionalProperties: false
+
+    OssieMetric:
+      type: object
+      description: A model-scoped quantitative measure
+      required:
+        - name
+        - expression
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The metric name, unique within the Semantic Model
+        expression:
+          $ref: "#/components/schemas/Expression"
+        description:
+          type: string
+          description: The human-readable metric description
+        datatype:
+          $ref: "#/components/schemas/SemanticModelDataType"
+        ai_context:
+          $ref: "#/components/schemas/AIContext"
+        custom_extensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. The property may be omitted; an 
explicitly supplied empty
+            array is retained as empty.
+          items:
+            $ref: "#/components/schemas/OssieCustomExtension"
+      additionalProperties: false
+
+    OssieDimension:
+      type: object
+      description: >-
+        Dimension metadata for a field. An empty object is valid and remains 
distinct from an
+        omitted dimension.
+      properties:
+        is_time:
+          type: boolean
+          description: Whether consumers should treat the field as a time 
dimension
+      additionalProperties: false
+
+    OssieCustomExtension:
+      type: object
+      description: Vendor-specific attributes retained for Semantic Model 
interchange
+      required:
+        - vendor_name
+        - data
+      properties:
+        vendor_name:
+          type: string
+          description: The extension vendor name
+        data:
+          type: string
+          description: Opaque vendor-specific data retained as a string
+      additionalProperties: false
+
+    SemanticModelDataType:
+      type: string
+      description: The exact wire value for a logical field or metric data type
+      enum:
+        - String
+        - Integer
+        - Decimal
+        - Float
+        - Boolean
+        - Date
+        - Time
+        - DateTime
+        - DateTimeTz
+        - Opaque
+
+    SemanticModelDefinition:
+      type: object
+      description: The complete replaceable semantic definition, without 
entity metadata
+      required:
+        - datasets
+      properties:
+        aiContext:
+          $ref: "#/components/schemas/AIContext"
+        datasets:
+          type: array
+          minItems: 1
+          description: The non-empty ordered datasets in the Semantic Model
+          items:
+            $ref: "#/components/schemas/Dataset"
+        relationships:
+          type: array
+          description: >-
+            Ordered relationships. The property may be omitted; an explicitly 
supplied empty array
+            is retained as empty.
+          items:
+            $ref: "#/components/schemas/Relationship"
+        metrics:
+          type: array
+          description: >-
+            Ordered metrics. The property may be omitted; an explicitly 
supplied empty array is
+            retained as empty.
+          items:
+            $ref: "#/components/schemas/Metric"
+        customExtensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. The root vendorName 
GRAVITINO_PROPERTIES is reserved and
+            cannot be supplied in a native definition. The property may be 
omitted; an explicitly
+            supplied empty array is retained as empty.
+          items:
+            $ref: "#/components/schemas/CustomExtension"
+      additionalProperties: false
+
+    OssieSemanticModelDocument:
+      type: object
+      description: >-
+        A standalone Apache Ossie document. Unlike the native Gravitino 
response, its semantic
+        definition is at the document root and it contains no response 
envelope, audit data, or
+        direct properties object.
+      required:
+        - version
+        - name
+        - datasets
+      properties:
+        version:
+          type: string
+          minLength: 1
+          description: >-
+            The non-blank Apache Ossie document version, stored in the 
ossie-version model property
+            on import. Export uses that property or defaults to 0.2.0.dev0 
when it is absent. The
+            version is preserved without restricting it to a fixed allowlist.
+        name:
+          type: string
+          minLength: 1
+          description: The Semantic Model name
+        description:
+          type: string
+          description: The human-readable Semantic Model description
+        ai_context:
+          $ref: "#/components/schemas/AIContext"
+        datasets:
+          type: array
+          minItems: 1
+          description: The non-empty ordered datasets in the Semantic Model
+          items:
+            $ref: "#/components/schemas/OssieDataset"
+        relationships:
+          type: array
+          description: >-
+            Ordered relationships. The property may be omitted; an explicitly 
supplied empty array
+            is retained as empty.
+          items:
+            $ref: "#/components/schemas/OssieRelationship"
+        metrics:
+          type: array
+          description: >-
+            Ordered metrics. The property may be omitted; an explicitly 
supplied empty array is
+            retained as empty.
+          items:
+            $ref: "#/components/schemas/OssieMetric"
+        custom_extensions:
+          type: array
+          description: >-
+            Vendor-specific extensions. At most one root GRAVITINO_PROPERTIES 
extension carries
+            Gravitino-specific properties as a JSON object encoded in a 
string, with string values
+            only. Import consumes this extension into model properties; an 
ossie-version value in
+            its data must match the document version. Export omits 
ossie-version from the extension
+            and writes it as version instead. Other extensions and nested 
GRAVITINO_PROPERTIES
+            extensions remain opaque. The property may be omitted; an 
explicitly supplied empty
+            array is normalized to an absent customExtensions in the native 
definition on import.
+          items:
+            $ref: "#/components/schemas/OssieCustomExtension"
+      additionalProperties: false
+
+    SemanticModel:
+      type: object
+      description: A schema-scoped analytical Semantic Model with a nested 
semantic definition
+      required:
+        - name
+        - definition
+        - properties
+        - audit
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The Semantic Model name
+        comment:
+          type: string
+          nullable: true
+          description: The comment; the property is omitted when no comment is 
set
+        definition:
+          $ref: "#/components/schemas/SemanticModelDefinition"
+        properties:
+          type: object
+          description: Gravitino-specific Semantic Model properties, including 
the Ossie version
+          additionalProperties:
+            type: string
+        audit:
+          $ref: "./openapi.yaml#/components/schemas/Audit"
+      additionalProperties: false
+
+    SemanticModelCreateRequest:
+      type: object
+      description: The request to create a Semantic Model
+      required:
+        - name
+        - definition
+      properties:
+        name:
+          type: string
+          minLength: 1
+          description: The non-blank Semantic Model name
+        comment:
+          type: string
+          nullable: true
+          description: The optional Semantic Model comment
+        definition:
+          $ref: "#/components/schemas/SemanticModelDefinition"
+        properties:
+          type: object
+          nullable: true
+          description: >-
+            Optional Gravitino-specific properties. Omitted or null properties 
use the defaults.
+            The ossie-version property must be non-blank and defaults to 
0.2.0.dev0.
+          additionalProperties:
+            type: string
+      additionalProperties: false
+
+    SemanticModelUpdatesRequest:
+      type: object
+      description: A non-empty ordered update batch applied atomically
+      required:
+        - updates
+      properties:
+        updates:
+          type: array
+          minItems: 1
+          items:
+            $ref: "#/components/schemas/SemanticModelUpdateRequest"
+      additionalProperties: false
+
+    SemanticModelUpdateRequest:
+      description: One polymorphic Semantic Model update
+      oneOf:
+        - $ref: "#/components/schemas/RenameSemanticModelRequest"
+        - $ref: "#/components/schemas/UpdateSemanticModelCommentRequest"
+        - $ref: "#/components/schemas/SetSemanticModelPropertyRequest"
+        - $ref: "#/components/schemas/RemoveSemanticModelPropertyRequest"
+        - $ref: "#/components/schemas/ReplaceSemanticModelDefinitionRequest"
+      discriminator:
+        propertyName: "@type"
+        mapping:
+          rename: "#/components/schemas/RenameSemanticModelRequest"
+          updateComment: 
"#/components/schemas/UpdateSemanticModelCommentRequest"
+          setProperty: "#/components/schemas/SetSemanticModelPropertyRequest"
+          removeProperty: 
"#/components/schemas/RemoveSemanticModelPropertyRequest"
+          replaceDefinition: 
"#/components/schemas/ReplaceSemanticModelDefinitionRequest"
+
+    RenameSemanticModelRequest:
+      type: object
+      description: Renames the Semantic Model
+      required:
+        - "@type"
+        - newName
+      properties:
+        "@type":
+          type: string
+          enum:
+            - rename
+        newName:
+          type: string
+          minLength: 1
+          description: The new non-blank Semantic Model name
+      example: {
+        "@type": "rename",
+        "newName": "sales_model_v2"
+      }
+
+    UpdateSemanticModelCommentRequest:
+      type: object
+      description: >-
+        Replaces the comment. An omitted or null newComment clears the 
comment; an explicit empty
+        string is stored as an empty comment.
+      required:
+        - "@type"
+      properties:
+        "@type":
+          type: string
+          enum:
+            - updateComment
+        newComment:
+          type: string
+          nullable: true
+          description: The new comment, null to clear it, or an empty string 
to retain empty
+      example: {
+        "@type": "updateComment",
+        "newComment": "Updated sales definitions"
+      }
+
+    SetSemanticModelPropertyRequest:
+      type: object
+      description: Sets or replaces one Gravitino-specific property
+      required:
+        - "@type"
+        - property
+        - value
+      properties:
+        "@type":
+          type: string
+          enum:
+            - setProperty
+        property:
+          type: string
+          minLength: 1
+          description: The non-blank property name
+        value:
+          type: string
+          description: The property value; an empty string is valid
+      example: {
+        "@type": "setProperty",
+        "property": "domain",
+        "value": "sales"
+      }
+
+    RemoveSemanticModelPropertyRequest:
+      type: object
+      description: Removes one Gravitino-specific property if it exists
+      required:
+        - "@type"
+        - property
+      properties:
+        "@type":
+          type: string
+          enum:
+            - removeProperty
+        property:
+          type: string
+          minLength: 1
+          description: The non-blank property name
+      example: {
+        "@type": "removeProperty",
+        "property": "legacy"
+      }
+
+    ReplaceSemanticModelDefinitionRequest:
+      type: object
+      description: Replaces and validates the complete nested definition 
atomically
+      required:
+        - "@type"
+        - definition
+      properties:
+        "@type":
+          type: string
+          enum:
+            - replaceDefinition
+        definition:
+          $ref: "#/components/schemas/SemanticModelDefinition"
+      example: {
+        "@type": "replaceDefinition",
+        "definition": {
+          "datasets": [
+            {
+              "name": "orders",
+              "source": {
+                "namespace": ["sales", "mart"],
+                "name": "orders"
+              },
+              "fields": []
+            }
+          ],
+          "relationships": [],
+          "metrics": [],
+          "customExtensions": []
+        }
+      }
+
+  responses:
+    SemanticModelListResponse:
+      description: Returns the Semantic Model identifiers in the schema
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            type: object
+            required:
+              - code
+              - identifiers
+            properties:
+              code:
+                type: integer
+                format: int32
+                description: Status code of the response
+                enum:
+                  - 0
+              identifiers:
+                type: array
+                description: Full Semantic Model identifiers
+                items:
+                  $ref: "./openapi.yaml#/components/schemas/NameIdentifier"
+            additionalProperties: false
+          examples:
+            SemanticModelListResponse:
+              $ref: "#/components/examples/SemanticModelListResponse"
+
+    SemanticModelResponse:
+      description: Returns the Semantic Model object with its nested definition
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            type: object
+            required:
+              - code
+              - semanticModel
+            properties:
+              code:
+                type: integer
+                format: int32
+                description: Status code of the response
+                enum:
+                  - 0
+              semanticModel:
+                $ref: "#/components/schemas/SemanticModel"
+            additionalProperties: false
+          examples:
+            SemanticModelResponse:
+              $ref: "#/components/examples/SemanticModelResponse"
+
+    OssieSemanticModelDocumentResponse:
+      description: Returns a standalone Apache Ossie document in the requested 
format
+      headers:
+        Content-Disposition:
+          description: >-
+            Attachment filename derived from the Semantic Model name, with 
characters outside
+            A-Za-z0-9._- replaced by underscores, ending in .ossie.yaml or 
.ossie.json
+          schema:
+            type: string
+            example: attachment; filename="sales_model.ossie.yaml"
+      content:
+        application/yaml:
+          schema:
+            $ref: "#/components/schemas/OssieSemanticModelDocument"
+          examples:
+            OssieSemanticModelDocument:
+              $ref: "#/components/examples/OssieSemanticModelDocument"
+        application/json:
+          schema:
+            $ref: "#/components/schemas/OssieSemanticModelDocument"
+          examples:
+            OssieSemanticModelDocument:
+              $ref: "#/components/examples/OssieSemanticModelDocument"
+
+    SemanticModelBadRequestResponse:
+      description: >-
+        Bad Request - The request is malformed or a Semantic Model definition, 
source, or update is
+        invalid
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+          examples:
+            IllegalSemanticModelException:
+              $ref: "#/components/examples/IllegalSemanticModelException"
+
+    SemanticModelForbiddenResponse:
+      description: Forbidden - The caller cannot access the requested or 
referenced metadata
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+          examples:
+            ForbiddenException:
+              $ref: "#/components/examples/ForbiddenException"
+
+    SemanticModelParentNotFoundResponse:
+      description: Not Found - The target metalake, catalog, or schema does 
not exist
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+          examples:
+            NoSuchMetalakeException:
+              $ref: 
"./metalakes.yaml#/components/examples/NoSuchMetalakeException"
+            NoSuchCatalogException:
+              $ref: 
"./catalogs.yaml#/components/examples/NoSuchCatalogException"
+            NoSuchSchemaException:
+              $ref: "./schemas.yaml#/components/examples/NoSuchSchemaException"
+
+    SemanticModelNotFoundResponse:
+      description: Not Found - The parent or target Semantic Model does not 
exist
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+          examples:
+            NoSuchMetalakeException:
+              $ref: 
"./metalakes.yaml#/components/examples/NoSuchMetalakeException"
+            NoSuchCatalogException:
+              $ref: 
"./catalogs.yaml#/components/examples/NoSuchCatalogException"
+            NoSuchSchemaException:
+              $ref: "./schemas.yaml#/components/examples/NoSuchSchemaException"
+            NoSuchSemanticModelException:
+              $ref: "#/components/examples/NoSuchSemanticModelException"
+
+    SemanticModelUnsupportedCatalogResponse:
+      description: Not Implemented - Semantic Models are supported only by 
relational catalogs
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+          examples:
+            UnsupportedOperationException:
+              $ref: "#/components/examples/UnsupportedOperationException"
+
+    SemanticModelAlreadyExistsResponse:
+      description: Conflict - The target Semantic Model already exists
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+          examples:
+            SemanticModelAlreadyExistsException:
+              $ref: "#/components/examples/SemanticModelAlreadyExistsException"
+
+    SemanticModelAlterConflictResponse:
+      description: >-
+        Conflict - A rename target already exists or the internal transaction 
lost an
+        optimistic-lock race
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+          examples:
+            SemanticModelAlreadyExistsException:
+              $ref: "#/components/examples/SemanticModelAlreadyExistsException"
+            OptimisticLockException:
+              $ref: "#/components/examples/OptimisticLockException"
+
+    SemanticModelOptimisticLockResponse:
+      description: Conflict - The internal transaction lost an optimistic-lock 
race
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+          examples:
+            OptimisticLockException:
+              $ref: "#/components/examples/OptimisticLockException"
+
+    SemanticModelConnectionFailedResponse:
+      description: Bad Gateway - A referenced source catalog is temporarily 
unavailable
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+          examples:
+            ConnectionFailedException:
+              $ref: "#/components/examples/ConnectionFailedException"
+
+  examples:
+    OssieSemanticModelDocument:
+      value: {
+        "version": "0.2.0.dev0",
+        "name": "sales_model",
+        "description": "Governed sales definitions",
+        "ai_context": {
+          "instructions": "Use certified sales metrics for revenue questions"
+        },
+        "datasets": [
+          {
+            "name": "orders",
+            "source": "sales.mart.orders",
+            "primary_key": ["order_id"],
+            "fields": [
+              {
+                "name": "order_amount",
+                "expression": {
+                  "dialects": [
+                    {
+                      "dialect": "ANSI_SQL",
+                      "expression": "order_amount"
+                    }
+                  ]
+                },
+                "datatype": "Decimal"
+              }
+            ]
+          }
+        ],
+        "relationships": [],
+        "metrics": [
+          {
+            "name": "total_revenue",
+            "expression": {
+              "dialects": [
+                {
+                  "dialect": "ANSI_SQL",
+                  "expression": "SUM(orders.order_amount)"
+                }
+              ]
+            },
+            "datatype": "Decimal"
+          }
+        ],
+        "custom_extensions": [
+          {
+            "vendor_name": "GRAVITINO_PROPERTIES",
+            "data": "{\"domain\":\"sales\"}"
+          }
+        ]
+      }
+
+    SemanticModelListResponse:
+      value: {
+        "code": 0,
+        "identifiers": [
+          {
+            "namespace": ["lake", "sales", "mart"],
+            "name": "sales_model"
+          }
+        ]
+      }
+
+    SemanticModelCreateRequest:
+      value: {
+        "name": "sales_model",
+        "comment": "Governed sales definitions",
+        "definition": {
+          "aiContext": {
+            "instructions": "Use certified sales metrics for revenue 
questions",
+            "synonyms": ["sales semantics"],
+            "confidence": 0.95
+          },
+          "datasets": [
+            {
+              "name": "orders",
+              "source": {
+                "namespace": ["sales", "mart"],
+                "name": "orders"
+              },
+              "primaryKey": ["order_id"],
+              "uniqueKeys": [],
+              "description": "Governed order facts",
+              "aiContext": "Use completed orders for revenue",
+              "fields": [
+                {
+                  "name": "order_time",
+                  "expression": {
+                    "dialects": [
+                      {
+                        "dialect": "ANSI_SQL",
+                        "expression": "order_time"
+                      }
+                    ]
+                  },
+                  "dimension": {
+                    "isTime": true
+                  },
+                  "label": "Order time",
+                  "datatype": "DateTimeTz"
+                },
+                {
+                  "name": "order_amount",
+                  "expression": {
+                    "dialects": [
+                      {
+                        "dialect": "TRINO",
+                        "expression": "order_amount"
+                      }
+                    ]
+                  },
+                  "description": "Order amount before refunds",
+                  "datatype": "Decimal",
+                  "customExtensions": [
+                    {
+                      "vendorName": "example",
+                      "data": "{\"semantic_type\":\"money\"}"
+                    }
+                  ]
+                }
+              ],
+              "customExtensions": []
+            },
+            {
+              "name": "customers",
+              "source": {
+                "namespace": ["sales", "mart"],
+                "name": "customers"
+              },
+              "fields": []
+            }
+          ],
+          "relationships": [
+            {
+              "name": "orders_to_customers",
+              "from": "orders",
+              "to": "customers",
+              "fromColumns": ["customer_id"],
+              "toColumns": ["customer_id"]
+            }
+          ],
+          "metrics": [
+            {
+              "name": "total_revenue",
+              "expression": {
+                "dialects": [
+                  {
+                    "dialect": "ANSI_SQL",
+                    "expression": "SUM(orders.order_amount)"
+                  }
+                ]
+              },
+              "description": "Total revenue across all orders",
+              "datatype": "Decimal",
+              "aiContext": {
+                "examples": ["What was revenue last month?"]
+              }
+            }
+          ],
+          "customExtensions": [
+            {
+              "vendorName": "example",
+              "data": "{\"certified\":true}"
+            }
+          ]
+        },
+        "properties": {
+          "domain": "sales"
+        }
+      }
+
+    SemanticModelUpdatesRequest:
+      value: {
+        "updates": [
+          {
+            "@type": "rename",
+            "newName": "sales_model_v2"
+          },
+          {
+            "@type": "updateComment",
+            "newComment": ""
+          },
+          {
+            "@type": "setProperty",
+            "property": "domain",
+            "value": "commerce"
+          },
+          {
+            "@type": "removeProperty",
+            "property": "legacy"
+          },
+          {
+            "@type": "replaceDefinition",
+            "definition": {
+              "datasets": [
+                {
+                  "name": "orders",
+                  "source": {
+                    "namespace": ["sales", "mart"],
+                    "name": "orders"
+                  },
+                  "fields": []
+                }
+              ],
+              "relationships": [],
+              "metrics": [],
+              "customExtensions": []
+            }
+          }
+        ]
+      }
+
+    SemanticModelResponse:
+      value: {
+        "code": 0,
+        "semanticModel": {
+          "name": "sales_model",
+          "comment": "Governed sales definitions",
+          "definition": {
+            "aiContext": "Use certified sales definitions",
+            "datasets": [
+              {
+                "name": "orders",
+                "source": {
+                  "namespace": ["sales", "mart"],
+                  "name": "orders"
+                },
+                "fields": []
+              }
+            ],
+            "relationships": [],
+            "metrics": [],
+            "customExtensions": []
+          },
+          "properties": {
+            "domain": "sales"
+          },
+          "audit": {
+            "creator": "gravitino",
+            "createTime": "2026-01-01T00:00:00Z",
+            "lastModifier": "gravitino",
+            "lastModifiedTime": "2026-01-01T00:00:00Z"
+          }
+        }
+      }
+
+    IllegalSemanticModelException:
+      value: {
+        "code": 1001,
+        "type": "IllegalSemanticModelException",
+        "message": "datasets[0].source: source must use catalog.schema.name",
+        "stack": [
+          "org.apache.gravitino.exceptions.IllegalSemanticModelException: 
datasets[0].source: source must use catalog.schema.name",
+          "..."
+        ]
+      }
+
+    NoSuchSemanticModelException:
+      value: {
+        "code": 1003,
+        "type": "NoSuchSemanticModelException",
+        "message": "Semantic Model does not exist: sales_model",
+        "stack": [
+          "org.apache.gravitino.exceptions.NoSuchSemanticModelException: 
Semantic Model does not exist: sales_model",
+          "..."
+        ]
+      }
+
+    SemanticModelAlreadyExistsException:
+      value: {
+        "code": 1004,
+        "type": "SemanticModelAlreadyExistsException",
+        "message": "Semantic Model already exists: sales_model",
+        "stack": [
+          
"org.apache.gravitino.exceptions.SemanticModelAlreadyExistsException: Semantic 
Model already exists: sales_model",
+          "..."
+        ]
+      }
+
+    UnsupportedOperationException:
+      value: {
+        "code": 1006,
+        "type": "UnsupportedOperationException",
+        "message": "The catalog does not support Semantic Model operations",
+        "stack": [
+          "java.lang.UnsupportedOperationException: The catalog does not 
support Semantic Model operations",
+          "..."
+        ]
+      }
+
+    ConnectionFailedException:
+      value: {
+        "code": 1007,
+        "type": "ConnectionFailedException",
+        "message": "A referenced source catalog is unavailable",
+        "stack": [
+          "org.apache.gravitino.exceptions.ConnectionFailedException: A 
referenced source catalog is unavailable",
+          "..."
+        ]
+      }
+
+    ForbiddenException:
+      value: {
+        "code": 1008,
+        "type": "ForbiddenException",
+        "message": "The caller cannot access referenced source metadata",
+        "stack": [
+          "org.apache.gravitino.exceptions.ForbiddenException: The caller 
cannot access referenced source metadata",
+          "..."
+        ]
+      }
+
+    OptimisticLockException:
+      value: {
+        "code": 1012,
+        "type": "OptimisticLockException",
+        "message": "The Semantic Model changed during the internal 
transaction",
+        "stack": [
+          "org.apache.gravitino.exceptions.OptimisticLockException: The 
Semantic Model changed during the internal transaction",
+          "..."
+        ]
+      }
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestSemanticModelOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestSemanticModelOperations.java
index 3fd2028c54..07d7c7ca50 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestSemanticModelOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestSemanticModelOperations.java
@@ -36,6 +36,10 @@ import static org.mockito.Mockito.when;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.json.JsonMapper;
 import java.io.IOException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.List;
 import java.util.Map;
@@ -44,6 +48,7 @@ import java.util.stream.Stream;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Application;
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.reflect.FieldUtils;
@@ -76,6 +81,7 @@ import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.exceptions.NoSuchViewException;
 import org.apache.gravitino.exceptions.OptimisticLockException;
 import org.apache.gravitino.exceptions.SemanticModelAlreadyExistsException;
+import org.apache.gravitino.json.JsonUtils;
 import org.apache.gravitino.meta.AuditInfo;
 import org.apache.gravitino.meta.SemanticModelEntity;
 import org.apache.gravitino.rel.Column;
@@ -1013,6 +1019,53 @@ public class TestSemanticModelOperations extends 
BaseOperationsTest {
     verifyNoMoreInteractions(dispatcher);
   }
 
+  @Test
+  void testImportOssieDocumentWithoutContentTypeDefaultsToYaml()
+      throws IOException, InterruptedException {
+    NameIdentifier ident = semanticModelIdentifier("marketing");
+    when(dispatcher.createSemanticModel(
+            eq(ident),
+            eq(null),
+            any(SemanticModelDefinition.class),
+            eq(Map.of(PROPERTY_OSSIE_VERSION, DEFAULT_VERSION))))
+        .thenReturn(semanticModel("marketing", null));
+
+    String yaml =
+        """
+        version: 0.2.0.dev0
+        name: marketing
+        datasets:
+          - name: campaigns
+            source: semantic_model_catalog.semantic_model_schema.campaigns
+            fields: []
+        """;
+    // The Jersey client may supply an entity media type, so send a raw HTTP 
request instead.
+    HttpRequest request =
+        HttpRequest.newBuilder(target(semanticModelPath() + "/ossie").getUri())
+            .timeout(Duration.ofSeconds(10))
+            .header(HttpHeaders.ACCEPT, VND_V1_JSON)
+            .POST(HttpRequest.BodyPublishers.ofString(yaml))
+            .build();
+    
Assertions.assertTrue(request.headers().firstValue(HttpHeaders.CONTENT_TYPE).isEmpty());
+
+    HttpResponse<String> response =
+        HttpClient.newHttpClient().send(request, 
HttpResponse.BodyHandlers.ofString());
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
response.statusCode());
+    SemanticModelResponse body =
+        JsonUtils.objectMapper().readValue(response.body(), 
SemanticModelResponse.class);
+    body.validate();
+    Assertions.assertEquals("marketing", body.getSemanticModel().name());
+    verify(dispatcher).importOssieDocument(namespace, 
OssieDocument.yaml(yaml));
+    verify(dispatcher)
+        .createSemanticModel(
+            eq(ident),
+            eq(null),
+            any(SemanticModelDefinition.class),
+            eq(Map.of(PROPERTY_OSSIE_VERSION, DEFAULT_VERSION)));
+    verifyNoMoreInteractions(dispatcher);
+  }
+
   @Test
   void testExportOssieYamlAndJsonDocuments() throws Exception {
     NameIdentifier ident = semanticModelIdentifier("sales");

Reply via email to