From c718aa8f0ca83b760b8165b11965b0a6cdc88961 Mon Sep 17 00:00:00 2001 From: "muhammad.fahlevi" Date: Tue, 5 May 2026 16:14:59 +0700 Subject: [PATCH 1/3] fix(change-detection): handling inner nested proto --- .../proto_change_detector_service.go | 90 ++- .../proto_change_detector_service_test.go | 47 ++ .../schema_with_nested_cross_ref_v1.proto | 20 + .../schema_with_nested_cross_ref_v2.proto | 21 + .../input/schema_with_nested_message_v1.proto | 14 + .../input/schema_with_nested_message_v2.proto | 15 + .../output/sce_nested_cross_ref_changed.json | 36 ++ .../output/sce_nested_message_changed.json | 35 + docs/docs/reference/api.md | 9 + .../stencil.local.postman_environment.json | 100 +++ .../assets/stencil.postman_collection.json | 612 ++++++++++++++++++ ui/embed.go | 2 +- 12 files changed, 976 insertions(+), 25 deletions(-) create mode 100644 core/changedetector/testdata/input/schema_with_nested_cross_ref_v1.proto create mode 100644 core/changedetector/testdata/input/schema_with_nested_cross_ref_v2.proto create mode 100644 core/changedetector/testdata/input/schema_with_nested_message_v1.proto create mode 100644 core/changedetector/testdata/input/schema_with_nested_message_v2.proto create mode 100644 core/changedetector/testdata/output/sce_nested_cross_ref_changed.json create mode 100644 core/changedetector/testdata/output/sce_nested_message_changed.json create mode 100644 docs/static/assets/stencil.local.postman_environment.json create mode 100644 docs/static/assets/stencil.postman_collection.json diff --git a/core/changedetector/proto_change_detector_service.go b/core/changedetector/proto_change_detector_service.go index a8cf0e27..14889d31 100644 --- a/core/changedetector/proto_change_detector_service.go +++ b/core/changedetector/proto_change_detector_service.go @@ -80,27 +80,59 @@ func appendImpactedDependents(sce *stencilv1beta1.SchemaChangedEvent, key string } func setDirectlyImpactedSchemasAndFields(currentFds, prevFds *descriptor.FileDescriptorSet, sce *stencilv1beta1.SchemaChangedEvent) { - packageMessageMap := getPackageMessageMap(prevFds) + prevMessageMap := collectAllMessages(prevFds) packageEnumMap := getPackageEnumMap(prevFds) for _, fileDesc := range currentFds.GetFile() { + pkg := fileDesc.GetPackage() for _, newMessageDesc := range fileDesc.GetMessageType() { - messageName := fileDesc.GetPackage() + "." + newMessageDesc.GetName() - oldMessageDesc := getMessageDescriptor(packageMessageMap, fileDesc.GetPackage(), newMessageDesc.GetName()) - if oldMessageDesc == nil { - sce.UpdatedSchemas = append(sce.UpdatedSchemas, messageName) - appendImpactedFields(sce, messageName, GetImpactedMessageFields(oldMessageDesc, newMessageDesc)) - continue - } - if !proto.Equal(oldMessageDesc, newMessageDesc) { - sce.UpdatedSchemas = append(sce.UpdatedSchemas, messageName) - appendImpactedFields(sce, messageName, GetImpactedMessageFields(oldMessageDesc, newMessageDesc)) - } - compareEnumDescInMessageDesc(oldMessageDesc, newMessageDesc, messageName, sce) + compareMessageTree(pkg, newMessageDesc, prevMessageMap, sce) } compareEnumDescriptors(fileDesc, packageEnumMap, sce) } } +// collectAllMessages returns a flat map of fully-qualified name -> descriptor +// for ALL messages (top-level and nested) across all files in the descriptor set. +func collectAllMessages(fds *descriptor.FileDescriptorSet) map[string]*descriptor.DescriptorProto { + result := make(map[string]*descriptor.DescriptorProto) + for _, fileDesc := range fds.GetFile() { + pkg := fileDesc.GetPackage() + for _, msgDesc := range fileDesc.GetMessageType() { + walkMessageTree(pkg, msgDesc, result) + } + } + return result +} + +// walkMessageTree recursively registers a message and all its nested messages +// into the result map under their fully-qualified names (e.g. "test.Outer.Inner"). +func walkMessageTree(prefix string, msg *descriptor.DescriptorProto, result map[string]*descriptor.DescriptorProto) { + fullName := prefix + "." + msg.GetName() + result[fullName] = msg + for _, nested := range msg.GetNestedType() { + walkMessageTree(fullName, nested, result) + } +} + +// compareMessageTree recursively compares a message and all its nested messages, +// recording any changes into the SchemaChangedEvent. +func compareMessageTree(prefix string, newMsg *descriptor.DescriptorProto, prevMessageMap map[string]*descriptor.DescriptorProto, sce *stencilv1beta1.SchemaChangedEvent) { + fullName := prefix + "." + newMsg.GetName() + oldMsg := prevMessageMap[fullName] + if oldMsg == nil { + sce.UpdatedSchemas = append(sce.UpdatedSchemas, fullName) + appendImpactedFields(sce, fullName, GetImpactedMessageFields(oldMsg, newMsg)) + } else if !proto.Equal(oldMsg, newMsg) { + sce.UpdatedSchemas = append(sce.UpdatedSchemas, fullName) + appendImpactedFields(sce, fullName, GetImpactedMessageFields(oldMsg, newMsg)) + compareEnumDescInMessageDesc(oldMsg, newMsg, fullName, sce) + } + // Always recurse into nested messages so each level is independently evaluated. + for _, nested := range newMsg.GetNestedType() { + compareMessageTree(fullName, nested, prevMessageMap, sce) + } +} + func compareEnumDescInMessageDesc(oldMessageDesc, newMessageDesc *descriptorpb.DescriptorProto, messageName string, sce *stencilv1beta1.SchemaChangedEvent) { for _, newEnumDesc := range newMessageDesc.GetEnumType() { enumName := messageName + "." + newEnumDesc.GetName() @@ -213,23 +245,33 @@ func findEnumDescriptorFromMessageDescriptor(messageDescriptor *descriptor.Descr func getReverseDependencies(fileDescriptorSet *descriptor.FileDescriptorSet) map[string][]string { reverseDependencies := make(map[string][]string) for _, fileDescriptor := range fileDescriptorSet.GetFile() { + pkg := fileDescriptor.GetPackage() for _, messageDescriptor := range fileDescriptor.GetMessageType() { - messageName := fileDescriptor.GetPackage() + "." + messageDescriptor.GetName() - for _, fieldDescriptor := range messageDescriptor.GetField() { - fieldType := fieldDescriptor.GetTypeName() - /*Check if the field type is a message (nested message or imported message) - Ref:https://cloud.google.com/java/docs/reference/protobuf/latest/com.google.protobuf.DescriptorProtos.FieldDescriptorProto#com_google_protobuf_DescriptorProtos_FieldDescriptorProto_getType__:~:text=The%20type.-,getTypeName(),-public%20String%20getTypeName - */ - if fieldType != "" && fieldType[0] == '.' { - dependentMessage := fieldType[1:] - reverseDependencies[dependentMessage] = append(reverseDependencies[dependentMessage], messageName) - } - } + buildReverseDepsFromMessage(pkg, messageDescriptor, reverseDependencies) } } return reverseDependencies } +// buildReverseDepsFromMessage recursively registers reverse dependencies for a message +// and all its nested messages, so that e.g. "test.Outer.Inner" is a valid lookup key. +func buildReverseDepsFromMessage(prefix string, msg *descriptor.DescriptorProto, reverseDeps map[string][]string) { + msgFullName := prefix + "." + msg.GetName() + for _, fieldDescriptor := range msg.GetField() { + fieldType := fieldDescriptor.GetTypeName() + /*Check if the field type is a message (nested message or imported message) + Ref:https://cloud.google.com/java/docs/reference/protobuf/latest/com.google.protobuf.DescriptorProtos.FieldDescriptorProto#com_google_protobuf_DescriptorProtos_FieldDescriptorProto_getType__:~:text=The%20type.-,getTypeName(),-public%20String%20getTypeName + */ + if fieldType != "" && fieldType[0] == '.' { + dependentMessage := fieldType[1:] + reverseDeps[dependentMessage] = append(reverseDeps[dependentMessage], msgFullName) + } + } + for _, nested := range msg.GetNestedType() { + buildReverseDepsFromMessage(msgFullName, nested, reverseDeps) + } +} + func getDependentImpactedSchemas(reverseDependencies map[string][]string, impactedSchema string, depth int32) []string { visitedMessages := make(map[string]bool) var dependentImpactedSchemas []string diff --git a/core/changedetector/proto_change_detector_service_test.go b/core/changedetector/proto_change_detector_service_test.go index 4cbbe3b7..6c6e8df0 100644 --- a/core/changedetector/proto_change_detector_service_test.go +++ b/core/changedetector/proto_change_detector_service_test.go @@ -250,6 +250,53 @@ func TestIdentifySchemaChange(t *testing.T) { }) } +func TestIdentifySchemaChangeNestedProto(t *testing.T) { + ctx := context.Background() + + t.Run("should detect field change inside nested message and report parent as impacted", func(t *testing.T) { + svc, newRelic := getSvc() + request.Depth = 10 + var called bool + // v1: Outer.Inner has only `value`. v2: Outer.Inner gains `extra_field`. + // Expected: both test.Outer and test.Outer.Inner appear in updated_schemas. + // test.Outer.Inner.updated_fields should contain "extra_field". + // test.Outer.Inner.impacted_schemas should contain test.Outer (it uses Inner). + oldData := getDescriptorData(t, "./testdata/input", true, []string{"schema_with_nested_message_v1.proto"}) + newData := getDescriptorData(t, "./testdata/input", true, []string{"schema_with_nested_message_v2.proto"}) + request.OldData = oldData + request.NewData = newData + newRelic.On("StartGenericSegment", mock.Anything, "Identify Schema Change").Return(func() { called = true }) + actual, err := svc.IdentifySchemaChange(ctx, request) + expected := getSchemaChangeEvent("./testdata/output/sce_nested_message_changed.json") + assert.Nil(t, err) + newRelic.AssertExpectations(t) + assert.True(t, called) + assert.NotNil(t, actual) + assertSchemaChangeEvent(t, expected, actual) + }) + + t.Run("should detect AnotherMessage as impacted when it references Outer.Inner which changed", func(t *testing.T) { + svc, newRelic := getSvc() + request.Depth = -1 + var called bool + // v1: Outer.Inner has only `value`. AnotherMessage uses Outer.Inner directly. + // v2: Outer.Inner gains `extra_field`. + // Expected: test.AnotherMessage appears in impacted_schemas["test.Outer.Inner"]. + oldData := getDescriptorData(t, "./testdata/input", true, []string{"schema_with_nested_cross_ref_v1.proto"}) + newData := getDescriptorData(t, "./testdata/input", true, []string{"schema_with_nested_cross_ref_v2.proto"}) + request.OldData = oldData + request.NewData = newData + newRelic.On("StartGenericSegment", mock.Anything, "Identify Schema Change").Return(func() { called = true }) + actual, err := svc.IdentifySchemaChange(ctx, request) + expected := getSchemaChangeEvent("./testdata/output/sce_nested_cross_ref_changed.json") + assert.Nil(t, err) + newRelic.AssertExpectations(t) + assert.True(t, called) + assert.NotNil(t, actual) + assertSchemaChangeEvent(t, expected, actual) + }) +} + func assertSchemaChangeEvent(t *testing.T, expected, actual *stencilv1beta1.SchemaChangedEvent) { t.Helper() assert.Equal(t, expected.NamespaceName, actual.NamespaceName) diff --git a/core/changedetector/testdata/input/schema_with_nested_cross_ref_v1.proto b/core/changedetector/testdata/input/schema_with_nested_cross_ref_v1.proto new file mode 100644 index 00000000..38f5e2d4 --- /dev/null +++ b/core/changedetector/testdata/input/schema_with_nested_cross_ref_v1.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package test; + +// Inner is defined INSIDE Outer +message Outer { + string id = 1; + + message Inner { + string value = 2; + } + + Inner inner = 3; +} + +// AnotherMessage uses Outer.Inner directly — cross-referencing a nested message +message AnotherMessage { + Outer.Inner inner_ref = 1; +} + diff --git a/core/changedetector/testdata/input/schema_with_nested_cross_ref_v2.proto b/core/changedetector/testdata/input/schema_with_nested_cross_ref_v2.proto new file mode 100644 index 00000000..3e20d6ee --- /dev/null +++ b/core/changedetector/testdata/input/schema_with_nested_cross_ref_v2.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package test; + +// Inner gains a new field `extra_field` +message Outer { + string id = 1; + + message Inner { + string value = 2; + string extra_field = 3; + } + + Inner inner = 3; +} + +// AnotherMessage still uses Outer.Inner — should be impacted by Inner's change +message AnotherMessage { + Outer.Inner inner_ref = 1; +} + diff --git a/core/changedetector/testdata/input/schema_with_nested_message_v1.proto b/core/changedetector/testdata/input/schema_with_nested_message_v1.proto new file mode 100644 index 00000000..acc07d25 --- /dev/null +++ b/core/changedetector/testdata/input/schema_with_nested_message_v1.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package test; + +message Outer { + string id = 1; + + message Inner { + string value = 2; + } + + Inner inner = 3; +} + diff --git a/core/changedetector/testdata/input/schema_with_nested_message_v2.proto b/core/changedetector/testdata/input/schema_with_nested_message_v2.proto new file mode 100644 index 00000000..ab5934b4 --- /dev/null +++ b/core/changedetector/testdata/input/schema_with_nested_message_v2.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package test; + +message Outer { + string id = 1; + + message Inner { + string value = 2; + string extra_field = 3; // new field added to nested message + } + + Inner inner = 3; +} + diff --git a/core/changedetector/testdata/output/sce_nested_cross_ref_changed.json b/core/changedetector/testdata/output/sce_nested_cross_ref_changed.json new file mode 100644 index 00000000..11234b96 --- /dev/null +++ b/core/changedetector/testdata/output/sce_nested_cross_ref_changed.json @@ -0,0 +1,36 @@ +{ + "namespace_name": "testNamespace", + "schema_name": "testSchemaName", + "updated_schemas": [ + "test.Outer", + "test.Outer.Inner" + ], + "updated_fields": { + "test.Outer": {}, + "test.Outer.Inner": { + "field_names": [ + "extra_field" + ] + } + }, + "impacted_schemas": { + "test.Outer": { + "schema_names": [ + "test.Outer" + ] + }, + "test.Outer.Inner": { + "schema_names": [ + "test.Outer.Inner", + "test.Outer", + "test.AnotherMessage" + ] + } + }, + "version": 1, + "metadata": { + "source_url": "https://github.com/some-repo", + "commit_sha": "some-commit-sha" + } +} + diff --git a/core/changedetector/testdata/output/sce_nested_message_changed.json b/core/changedetector/testdata/output/sce_nested_message_changed.json new file mode 100644 index 00000000..719a5603 --- /dev/null +++ b/core/changedetector/testdata/output/sce_nested_message_changed.json @@ -0,0 +1,35 @@ +{ + "namespace_name": "testNamespace", + "schema_name": "testSchemaName", + "updated_schemas": [ + "test.Outer", + "test.Outer.Inner" + ], + "updated_fields": { + "test.Outer": {}, + "test.Outer.Inner": { + "field_names": [ + "extra_field" + ] + } + }, + "impacted_schemas": { + "test.Outer": { + "schema_names": [ + "test.Outer" + ] + }, + "test.Outer.Inner": { + "schema_names": [ + "test.Outer.Inner", + "test.Outer" + ] + } + }, + "version": 1, + "metadata": { + "source_url": "https://github.com/some-repo", + "commit_sha": "some-commit-sha" + } +} + diff --git a/docs/docs/reference/api.md b/docs/docs/reference/api.md index b9e03094..29fee6e3 100644 --- a/docs/docs/reference/api.md +++ b/docs/docs/reference/api.md @@ -2,6 +2,15 @@ ## Version: 0.8.7 +## Postman import + +- Collection: [`stencil.postman_collection.json`](/assets/stencil.postman_collection.json) +- Local environment: [`stencil.local.postman_environment.json`](/assets/stencil.local.postman_environment.json) + +The collection defaults `baseUrl` to `http://localhost:8080`, which matches the server default in `config/config.go`. If your Stencil instance is running on another port such as `8000`, update `baseUrl` in Postman after import. + +For schema upload and compatibility-check requests, set `schemaBinaryFile` to a local file path in Postman before sending the request. + ### /v1beta1/namespaces #### GET diff --git a/docs/static/assets/stencil.local.postman_environment.json b/docs/static/assets/stencil.local.postman_environment.json new file mode 100644 index 00000000..fe947ff6 --- /dev/null +++ b/docs/static/assets/stencil.local.postman_environment.json @@ -0,0 +1,100 @@ +{ + "id": "8fb5760d-4d5d-4057-9561-8c49760324b1", + "name": "Stencil Local", + "values": [ + { + "key": "baseUrl", + "value": "http://localhost:8080", + "type": "default", + "enabled": true + }, + { + "key": "namespaceId", + "value": "quickstart", + "type": "default", + "enabled": true + }, + { + "key": "schemaId", + "value": "example", + "type": "default", + "enabled": true + }, + { + "key": "versionId", + "value": "1", + "type": "default", + "enabled": true + }, + { + "key": "schemaFormat", + "value": "FORMAT_PROTOBUF", + "type": "default", + "enabled": true + }, + { + "key": "schemaCompatibility", + "value": "COMPATIBILITY_BACKWARD", + "type": "default", + "enabled": true + }, + { + "key": "schemaBinaryFile", + "value": "", + "type": "default", + "enabled": true + }, + { + "key": "sourceUrl", + "value": "https://github.com/your-org/your-repo", + "type": "default", + "enabled": true + }, + { + "key": "commitSHA", + "value": "HEAD", + "type": "default", + "enabled": true + }, + { + "key": "searchQuery", + "value": "message", + "type": "default", + "enabled": true + }, + { + "key": "searchHistory", + "value": "false", + "type": "default", + "enabled": true + }, + { + "key": "searchVersionId", + "value": "", + "type": "default", + "enabled": true + }, + { + "key": "detectFrom", + "value": "", + "type": "default", + "enabled": true + }, + { + "key": "detectTo", + "value": "", + "type": "default", + "enabled": true + }, + { + "key": "detectDepth", + "value": "", + "type": "default", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-04-16T00:00:00.000Z", + "_postman_exported_using": "GitHub Copilot" +} + diff --git a/docs/static/assets/stencil.postman_collection.json b/docs/static/assets/stencil.postman_collection.json new file mode 100644 index 00000000..362cde29 --- /dev/null +++ b/docs/static/assets/stencil.postman_collection.json @@ -0,0 +1,612 @@ +{ + "info": { + "_postman_id": "2baeb15d-2ef3-4172-a0cb-2a4e3c8d53b7", + "name": "Stencil API", + "description": "Postman collection for the Stencil HTTP API. The default `baseUrl` is `http://localhost:8080`, which matches the server configuration in `config/config.go`. If you run Stencil on another port such as `8000`, update the environment variable after import.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:8080", + "type": "string" + }, + { + "key": "namespaceId", + "value": "quickstart", + "type": "string" + }, + { + "key": "schemaId", + "value": "example", + "type": "string" + }, + { + "key": "versionId", + "value": "1", + "type": "string" + }, + { + "key": "schemaFormat", + "value": "FORMAT_PROTOBUF", + "type": "string" + }, + { + "key": "schemaCompatibility", + "value": "COMPATIBILITY_BACKWARD", + "type": "string" + }, + { + "key": "schemaBinaryFile", + "value": "", + "type": "string" + }, + { + "key": "sourceUrl", + "value": "https://github.com/your-org/your-repo", + "type": "string" + }, + { + "key": "commitSHA", + "value": "HEAD", + "type": "string" + }, + { + "key": "searchQuery", + "value": "message", + "type": "string" + }, + { + "key": "searchHistory", + "value": "false", + "type": "string" + }, + { + "key": "searchVersionId", + "value": "", + "type": "string" + }, + { + "key": "detectFrom", + "value": "", + "type": "string" + }, + { + "key": "detectTo", + "value": "", + "type": "string" + }, + { + "key": "detectDepth", + "value": "", + "type": "string" + } + ], + "item": [ + { + "name": "Health", + "item": [ + { + "name": "Ping", + "request": { + "method": "GET", + "description": "Health-check endpoint registered directly by the HTTP server.", + "header": [], + "url": { + "raw": "{{baseUrl}}/ping", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "ping" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Namespaces", + "item": [ + { + "name": "List namespaces", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces" + ] + } + }, + "response": [] + }, + { + "name": "Create namespace", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": \"{{namespaceId}}\",\n \"format\": \"{{schemaFormat}}\",\n \"compatibility\": \"{{schemaCompatibility}}\",\n \"description\": \"Namespace created from the Postman collection\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces" + ] + } + }, + "response": [] + }, + { + "name": "Get namespace", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update namespace", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"format\": \"{{schemaFormat}}\",\n \"compatibility\": \"{{schemaCompatibility}}\",\n \"description\": \"Updated namespace description\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete namespace", + "request": { + "method": "DELETE", + "description": "Deletes the namespace. The API requires schemas under the namespace to be deleted first.", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Schemas", + "item": [ + { + "name": "List schemas", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas" + ] + } + }, + "response": [] + }, + { + "name": "Get latest schema", + "request": { + "method": "GET", + "description": "Returns JSON for Avro/JSON schema namespaces and binary data for Protobuf namespaces.", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas", + "{{schemaId}}" + ] + } + }, + "response": [] + }, + { + "name": "Get schema by version", + "request": { + "method": "GET", + "description": "This route is registered by the server even though it is not present in the checked-in Swagger file.", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/versions/{{versionId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas", + "{{schemaId}}", + "versions", + "{{versionId}}" + ] + } + }, + "response": [] + }, + { + "name": "Create schema", + "request": { + "method": "POST", + "description": "Select a local file for `schemaBinaryFile` in Postman. Optional metadata headers are prefilled as variables.", + "header": [ + { + "key": "Content-Type", + "value": "application/octet-stream" + }, + { + "key": "X-Format", + "value": "{{schemaFormat}}" + }, + { + "key": "X-Compatibility", + "value": "{{schemaCompatibility}}" + }, + { + "key": "X-SourceURL", + "value": "{{sourceUrl}}" + }, + { + "key": "X-CommitSHA", + "value": "{{commitSHA}}" + } + ], + "body": { + "mode": "file", + "file": { + "src": "{{schemaBinaryFile}}" + } + }, + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas", + "{{schemaId}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete schema", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas", + "{{schemaId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update schema metadata", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"compatibility\": \"{{schemaCompatibility}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas", + "{{schemaId}}" + ] + } + }, + "response": [] + }, + { + "name": "Check compatibility", + "request": { + "method": "POST", + "description": "Select a local schema file before sending this request.", + "header": [ + { + "key": "Content-Type", + "value": "application/octet-stream" + }, + { + "key": "X-Compatibility", + "value": "{{schemaCompatibility}}" + } + ], + "body": { + "mode": "file", + "file": { + "src": "{{schemaBinaryFile}}" + } + }, + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/check", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas", + "{{schemaId}}", + "check" + ] + } + }, + "response": [] + }, + { + "name": "Get schema metadata", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/meta", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas", + "{{schemaId}}", + "meta" + ] + } + }, + "response": [] + }, + { + "name": "List versions", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/versions", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas", + "{{schemaId}}", + "versions" + ] + } + }, + "response": [] + }, + { + "name": "Delete version", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/versions/{{versionId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "namespaces", + "{{namespaceId}}", + "schemas", + "{{schemaId}}", + "versions", + "{{versionId}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Search", + "item": [ + { + "name": "Global search", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/search?query={{searchQuery}}&namespaceId={{namespaceId}}&schemaId={{schemaId}}&history={{searchHistory}}&versionId={{searchVersionId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "search" + ], + "query": [ + { + "key": "query", + "value": "{{searchQuery}}" + }, + { + "key": "namespaceId", + "value": "{{namespaceId}}", + "disabled": true + }, + { + "key": "schemaId", + "value": "{{schemaId}}", + "disabled": true + }, + { + "key": "history", + "value": "{{searchHistory}}", + "disabled": true + }, + { + "key": "versionId", + "value": "{{searchVersionId}}", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Change detection", + "item": [ + { + "name": "Detect schema change", + "request": { + "method": "GET", + "description": "Registered by the HTTP server and documented in `docs/docs/reference/api.md`, but not present in the checked-in Swagger file.", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1beta1/schema/detect-change/{{namespaceId}}/{{schemaId}}?from={{detectFrom}}&to={{detectTo}}&depth={{detectDepth}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1beta1", + "schema", + "detect-change", + "{{namespaceId}}", + "{{schemaId}}" + ], + "query": [ + { + "key": "from", + "value": "{{detectFrom}}", + "disabled": true + }, + { + "key": "to", + "value": "{{detectTo}}", + "disabled": true + }, + { + "key": "depth", + "value": "{{detectDepth}}", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ] +} + diff --git a/ui/embed.go b/ui/embed.go index 540dc893..5065dec7 100644 --- a/ui/embed.go +++ b/ui/embed.go @@ -4,5 +4,5 @@ import "embed" // Assets embed build folder // -//go:embed build/* +//go:embed all:build var Assets embed.FS From f62161696db4aa1694b1d3c7647dcf770366a77c Mon Sep 17 00:00:00 2001 From: "muhammad.fahlevi" Date: Tue, 5 May 2026 16:34:16 +0700 Subject: [PATCH 2/3] fix: lint issue --- .../proto_change_detector_service.go | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/core/changedetector/proto_change_detector_service.go b/core/changedetector/proto_change_detector_service.go index 14889d31..2107b1b2 100644 --- a/core/changedetector/proto_change_detector_service.go +++ b/core/changedetector/proto_change_detector_service.go @@ -179,24 +179,6 @@ func compareEnumDescriptors(fds *descriptorpb.FileDescriptorProto, packageEnumMa } } -/* -packageMessageMap is map having all the messages inside a package -[com.goto.bookinglog][BookingLogMessage]=BookingLogMessageDescriptor -*/ -func getPackageMessageMap(fileDescriptorSet *descriptor.FileDescriptorSet) map[string]map[string]*descriptor.DescriptorProto { - packageMessageMap := make(map[string]map[string]*descriptor.DescriptorProto) - for _, fileDescriptor := range fileDescriptorSet.GetFile() { - pkgName := fileDescriptor.GetPackage() - if _, ok := packageMessageMap[pkgName]; !ok { - packageMessageMap[pkgName] = make(map[string]*descriptor.DescriptorProto) - } - for _, messageDescriptor := range fileDescriptor.GetMessageType() { - packageMessageMap[pkgName][messageDescriptor.GetName()] = messageDescriptor - } - } - return packageMessageMap -} - /* packageEnumMap is map having all the enums inside a package [com.goto.bookinglog][ServiceTypeEnum]=ServiceTypeEnumDescriptor @@ -215,15 +197,6 @@ func getPackageEnumMap(fileDescriptorSet *descriptor.FileDescriptorSet) map[stri return packageEnumMap } -func getMessageDescriptor(packageMessageMap map[string]map[string]*descriptor.DescriptorProto, packageName, messageName string) *descriptor.DescriptorProto { - if packageMap, found := packageMessageMap[packageName]; found { - if descriptor, found := packageMap[messageName]; found { - return descriptor - } - } - return nil -} - func getEnumDescriptor(packageEnumMap map[string]map[string]*descriptor.EnumDescriptorProto, packageName, enumName string) *descriptor.EnumDescriptorProto { if packageMap, found := packageEnumMap[packageName]; found { if descriptor, found := packageMap[enumName]; found { From 282a6705b7f2587d5287539605fcf7e6fcad87de Mon Sep 17 00:00:00 2001 From: "muhammad.fahlevi" Date: Mon, 11 May 2026 16:03:11 +0700 Subject: [PATCH 3/3] feat: remove postman --- docs/docs/reference/api.md | 9 - .../stencil.local.postman_environment.json | 100 --- .../assets/stencil.postman_collection.json | 612 ------------------ 3 files changed, 721 deletions(-) delete mode 100644 docs/static/assets/stencil.local.postman_environment.json delete mode 100644 docs/static/assets/stencil.postman_collection.json diff --git a/docs/docs/reference/api.md b/docs/docs/reference/api.md index 29fee6e3..b9e03094 100644 --- a/docs/docs/reference/api.md +++ b/docs/docs/reference/api.md @@ -2,15 +2,6 @@ ## Version: 0.8.7 -## Postman import - -- Collection: [`stencil.postman_collection.json`](/assets/stencil.postman_collection.json) -- Local environment: [`stencil.local.postman_environment.json`](/assets/stencil.local.postman_environment.json) - -The collection defaults `baseUrl` to `http://localhost:8080`, which matches the server default in `config/config.go`. If your Stencil instance is running on another port such as `8000`, update `baseUrl` in Postman after import. - -For schema upload and compatibility-check requests, set `schemaBinaryFile` to a local file path in Postman before sending the request. - ### /v1beta1/namespaces #### GET diff --git a/docs/static/assets/stencil.local.postman_environment.json b/docs/static/assets/stencil.local.postman_environment.json deleted file mode 100644 index fe947ff6..00000000 --- a/docs/static/assets/stencil.local.postman_environment.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "id": "8fb5760d-4d5d-4057-9561-8c49760324b1", - "name": "Stencil Local", - "values": [ - { - "key": "baseUrl", - "value": "http://localhost:8080", - "type": "default", - "enabled": true - }, - { - "key": "namespaceId", - "value": "quickstart", - "type": "default", - "enabled": true - }, - { - "key": "schemaId", - "value": "example", - "type": "default", - "enabled": true - }, - { - "key": "versionId", - "value": "1", - "type": "default", - "enabled": true - }, - { - "key": "schemaFormat", - "value": "FORMAT_PROTOBUF", - "type": "default", - "enabled": true - }, - { - "key": "schemaCompatibility", - "value": "COMPATIBILITY_BACKWARD", - "type": "default", - "enabled": true - }, - { - "key": "schemaBinaryFile", - "value": "", - "type": "default", - "enabled": true - }, - { - "key": "sourceUrl", - "value": "https://github.com/your-org/your-repo", - "type": "default", - "enabled": true - }, - { - "key": "commitSHA", - "value": "HEAD", - "type": "default", - "enabled": true - }, - { - "key": "searchQuery", - "value": "message", - "type": "default", - "enabled": true - }, - { - "key": "searchHistory", - "value": "false", - "type": "default", - "enabled": true - }, - { - "key": "searchVersionId", - "value": "", - "type": "default", - "enabled": true - }, - { - "key": "detectFrom", - "value": "", - "type": "default", - "enabled": true - }, - { - "key": "detectTo", - "value": "", - "type": "default", - "enabled": true - }, - { - "key": "detectDepth", - "value": "", - "type": "default", - "enabled": true - } - ], - "_postman_variable_scope": "environment", - "_postman_exported_at": "2026-04-16T00:00:00.000Z", - "_postman_exported_using": "GitHub Copilot" -} - diff --git a/docs/static/assets/stencil.postman_collection.json b/docs/static/assets/stencil.postman_collection.json deleted file mode 100644 index 362cde29..00000000 --- a/docs/static/assets/stencil.postman_collection.json +++ /dev/null @@ -1,612 +0,0 @@ -{ - "info": { - "_postman_id": "2baeb15d-2ef3-4172-a0cb-2a4e3c8d53b7", - "name": "Stencil API", - "description": "Postman collection for the Stencil HTTP API. The default `baseUrl` is `http://localhost:8080`, which matches the server configuration in `config/config.go`. If you run Stencil on another port such as `8000`, update the environment variable after import.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "variable": [ - { - "key": "baseUrl", - "value": "http://localhost:8080", - "type": "string" - }, - { - "key": "namespaceId", - "value": "quickstart", - "type": "string" - }, - { - "key": "schemaId", - "value": "example", - "type": "string" - }, - { - "key": "versionId", - "value": "1", - "type": "string" - }, - { - "key": "schemaFormat", - "value": "FORMAT_PROTOBUF", - "type": "string" - }, - { - "key": "schemaCompatibility", - "value": "COMPATIBILITY_BACKWARD", - "type": "string" - }, - { - "key": "schemaBinaryFile", - "value": "", - "type": "string" - }, - { - "key": "sourceUrl", - "value": "https://github.com/your-org/your-repo", - "type": "string" - }, - { - "key": "commitSHA", - "value": "HEAD", - "type": "string" - }, - { - "key": "searchQuery", - "value": "message", - "type": "string" - }, - { - "key": "searchHistory", - "value": "false", - "type": "string" - }, - { - "key": "searchVersionId", - "value": "", - "type": "string" - }, - { - "key": "detectFrom", - "value": "", - "type": "string" - }, - { - "key": "detectTo", - "value": "", - "type": "string" - }, - { - "key": "detectDepth", - "value": "", - "type": "string" - } - ], - "item": [ - { - "name": "Health", - "item": [ - { - "name": "Ping", - "request": { - "method": "GET", - "description": "Health-check endpoint registered directly by the HTTP server.", - "header": [], - "url": { - "raw": "{{baseUrl}}/ping", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "ping" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Namespaces", - "item": [ - { - "name": "List namespaces", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces" - ] - } - }, - "response": [] - }, - { - "name": "Create namespace", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"id\": \"{{namespaceId}}\",\n \"format\": \"{{schemaFormat}}\",\n \"compatibility\": \"{{schemaCompatibility}}\",\n \"description\": \"Namespace created from the Postman collection\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces" - ] - } - }, - "response": [] - }, - { - "name": "Get namespace", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}" - ] - } - }, - "response": [] - }, - { - "name": "Update namespace", - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"format\": \"{{schemaFormat}}\",\n \"compatibility\": \"{{schemaCompatibility}}\",\n \"description\": \"Updated namespace description\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete namespace", - "request": { - "method": "DELETE", - "description": "Deletes the namespace. The API requires schemas under the namespace to be deleted first.", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Schemas", - "item": [ - { - "name": "List schemas", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas" - ] - } - }, - "response": [] - }, - { - "name": "Get latest schema", - "request": { - "method": "GET", - "description": "Returns JSON for Avro/JSON schema namespaces and binary data for Protobuf namespaces.", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas", - "{{schemaId}}" - ] - } - }, - "response": [] - }, - { - "name": "Get schema by version", - "request": { - "method": "GET", - "description": "This route is registered by the server even though it is not present in the checked-in Swagger file.", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/versions/{{versionId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas", - "{{schemaId}}", - "versions", - "{{versionId}}" - ] - } - }, - "response": [] - }, - { - "name": "Create schema", - "request": { - "method": "POST", - "description": "Select a local file for `schemaBinaryFile` in Postman. Optional metadata headers are prefilled as variables.", - "header": [ - { - "key": "Content-Type", - "value": "application/octet-stream" - }, - { - "key": "X-Format", - "value": "{{schemaFormat}}" - }, - { - "key": "X-Compatibility", - "value": "{{schemaCompatibility}}" - }, - { - "key": "X-SourceURL", - "value": "{{sourceUrl}}" - }, - { - "key": "X-CommitSHA", - "value": "{{commitSHA}}" - } - ], - "body": { - "mode": "file", - "file": { - "src": "{{schemaBinaryFile}}" - } - }, - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas", - "{{schemaId}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete schema", - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas", - "{{schemaId}}" - ] - } - }, - "response": [] - }, - { - "name": "Update schema metadata", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"compatibility\": \"{{schemaCompatibility}}\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas", - "{{schemaId}}" - ] - } - }, - "response": [] - }, - { - "name": "Check compatibility", - "request": { - "method": "POST", - "description": "Select a local schema file before sending this request.", - "header": [ - { - "key": "Content-Type", - "value": "application/octet-stream" - }, - { - "key": "X-Compatibility", - "value": "{{schemaCompatibility}}" - } - ], - "body": { - "mode": "file", - "file": { - "src": "{{schemaBinaryFile}}" - } - }, - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/check", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas", - "{{schemaId}}", - "check" - ] - } - }, - "response": [] - }, - { - "name": "Get schema metadata", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/meta", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas", - "{{schemaId}}", - "meta" - ] - } - }, - "response": [] - }, - { - "name": "List versions", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/versions", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas", - "{{schemaId}}", - "versions" - ] - } - }, - "response": [] - }, - { - "name": "Delete version", - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/namespaces/{{namespaceId}}/schemas/{{schemaId}}/versions/{{versionId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "namespaces", - "{{namespaceId}}", - "schemas", - "{{schemaId}}", - "versions", - "{{versionId}}" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Search", - "item": [ - { - "name": "Global search", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/search?query={{searchQuery}}&namespaceId={{namespaceId}}&schemaId={{schemaId}}&history={{searchHistory}}&versionId={{searchVersionId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "search" - ], - "query": [ - { - "key": "query", - "value": "{{searchQuery}}" - }, - { - "key": "namespaceId", - "value": "{{namespaceId}}", - "disabled": true - }, - { - "key": "schemaId", - "value": "{{schemaId}}", - "disabled": true - }, - { - "key": "history", - "value": "{{searchHistory}}", - "disabled": true - }, - { - "key": "versionId", - "value": "{{searchVersionId}}", - "disabled": true - } - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Change detection", - "item": [ - { - "name": "Detect schema change", - "request": { - "method": "GET", - "description": "Registered by the HTTP server and documented in `docs/docs/reference/api.md`, but not present in the checked-in Swagger file.", - "header": [], - "url": { - "raw": "{{baseUrl}}/v1beta1/schema/detect-change/{{namespaceId}}/{{schemaId}}?from={{detectFrom}}&to={{detectTo}}&depth={{detectDepth}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1beta1", - "schema", - "detect-change", - "{{namespaceId}}", - "{{schemaId}}" - ], - "query": [ - { - "key": "from", - "value": "{{detectFrom}}", - "disabled": true - }, - { - "key": "to", - "value": "{{detectTo}}", - "disabled": true - }, - { - "key": "depth", - "value": "{{detectDepth}}", - "disabled": true - } - ] - } - }, - "response": [] - } - ] - } - ] -} -