Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion openmetadata-go-client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@
go.work

# models generated by OpenAPI Generator
**/*/models.go
**/*/models.go

# IDEs
.idea

# Schema sources
schemas/
5 changes: 5 additions & 0 deletions openmetadata-go-client/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@ go_unit_tests: ## Run go unit tests
go_integration_tests: ## Run go integration tests
cd $(ROOT_DIR)/$(GO_DIRECTORY) && go test -v ./tests/integration/

.PHONE: go_patch_script_tests
go_patch_script_tests: ## Run tests for the patching script
cd $(ROOT_DIR)/$(GO_DIRECTORY) && go test -v ./scripts/

.PHONY: go_tests
go_tests: ## Run linter + all tests
$(MAKE) go_lint
$(MAKE) go_unit_tests
$(MAKE) go_patch_script_tests
$(MAKE) go_integration_tests
64 changes: 64 additions & 0 deletions openmetadata-go-client/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,69 @@
# OpenMetadata Go Client

## Development

### Prerequisites

- Go 1.24+
- Access to an OpenMetadata server (for the swagger spec)

### Code Generation

The `pkg/ometa/models.go` file is generated from the OpenMetadata swagger spec. To regenerate it:

**1. Get the swagger spec** from a running OpenMetadata instance:

```bash
curl http://localhost:8585/api/swagger.json -o schemas/swagger.json
```

Or copy it out of a Docker container:

```bash
docker run --rm -d --name om-tmp openmetadata/server:1.6.2 sleep 30
docker cp om-tmp:/opt/openmetadata/openmetadata-service/target/classes/assets/swagger.json ./schemas/swagger.json
docker rm -f om-tmp
```

**2. Patch the spec** (converts bare objects and date-time fields to Go-friendly types):

```bash
go run scripts/patch_swagger.go schemas/swagger.json schemas/patched.json
```

**3. Generate models:**

```bash
go tool oapi-codegen \
-generate types \
-package ometa \
-o pkg/ometa/models.go \
schemas/patched.json
```

### Running Tests

```bash
# Unit tests
make go_unit_tests

# Patch script tests
make go_patch_script_tests

# Integration tests (requires a running OM server with OM_TEST_TOKEN set)
make go_integration_tests

# Run test suite
make go_tests
```

### Linting

```bash
make go_lint # check
make go_lint_fix # auto-fix
```

## Installation

```bash
Expand Down
87 changes: 44 additions & 43 deletions openmetadata-go-client/scripts/patch_swagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,23 @@ func isBareObject(v interface{}) bool {
return ok && t == "object"
}

// walkSchema recursively visits every schema node in the tree, calling fn on
// each map[string]interface{} encountered. This covers properties, allOf/anyOf/oneOf
// entries, array items, and any other inline schema.
func walkSchema(node interface{}, fn func(m map[string]interface{})) {
switch v := node.(type) {
case map[string]interface{}:
fn(v)
for _, child := range v {
walkSchema(child, fn)
}
case []interface{}:
for _, elem := range v {
walkSchema(elem, fn)
}
}
}

// patchBareObjects removes "type": "object" from properties that are bare objects
// (no "properties", "$ref", "additionalProperties", etc.). These represent free-form
// values in the OpenMetadata API.
Expand All @@ -96,36 +113,31 @@ func patchBareObjects(schemas map[string]interface{}) int {
count := 0

for _, schemaDef := range schemas {
schemaMap, ok := schemaDef.(map[string]interface{})
if !ok {
continue
}
properties, ok := schemaMap["properties"].(map[string]interface{})
if !ok {
continue
}

for propName, propValue := range properties {
propMap, ok := propValue.(map[string]interface{})
walkSchema(schemaDef, func(m map[string]interface{}) {
props, ok := m["properties"].(map[string]interface{})
if !ok {
continue
}

// Case 1: property is exactly {"type": "object"}
if isBareObject(propValue) {
properties[propName] = map[string]interface{}{}
count++
continue
return
}
for propName, propValue := range props {
propMap, ok := propValue.(map[string]interface{})
if !ok {
continue
}

// Case 2: property is {"type": "array", "items": {"type": "object"}}
if t, _ := propMap["type"].(string); t == "array" {
if isBareObject(propMap["items"]) {
propMap["items"] = map[string]interface{}{}
if isBareObject(propValue) {
props[propName] = map[string]interface{}{}
count++
continue
}

if t, _ := propMap["type"].(string); t == "array" {
if isBareObject(propMap["items"]) {
propMap["items"] = map[string]interface{}{}
count++
}
}
}
}
})
}

return count
Expand All @@ -135,31 +147,20 @@ func patchBareObjects(schemas map[string]interface{}) int {
// integer/int64 so oapi-codegen generates *int64 instead of *time.Time.
// The OpenMetadata API returns epoch milliseconds for these fields, which
// causes time.Time.UnmarshalJSON to fail.
//
// Walks the entire schema tree so fields nested inside allOf/anyOf/oneOf or
// inline object properties are also patched.
func patchDateTimeToEpochMillis(schemas map[string]interface{}) int {
count := 0

for _, schemaDef := range schemas {
schemaMap, ok := schemaDef.(map[string]interface{})
if !ok {
continue
}
properties, ok := schemaMap["properties"].(map[string]interface{})
if !ok {
continue
}

for _, propValue := range properties {
propMap, ok := propValue.(map[string]interface{})
if !ok {
continue
}

if fmtVal, hasFmt := propMap["format"].(string); hasFmt && fmtVal == "date-time" {
propMap["type"] = "integer"
propMap["format"] = "int64"
walkSchema(schemaDef, func(m map[string]interface{}) {
if fmtVal, ok := m["format"].(string); ok && fmtVal == "date-time" {
m["type"] = "integer"
m["format"] = "int64"
count++
}
}
})
}

return count
Expand Down
Loading
Loading