Skip to content

Commit 2ff3db9

Browse files
Make query.schema.json compliant with openapi v2 (#1297)
1 parent a259df9 commit 2ff3db9

File tree

8 files changed

+126
-86
lines changed

8 files changed

+126
-86
lines changed

experimental/apis/data/v0alpha1/query.definition.schema.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{
2-
"$schema": "https://json-schema.org/draft-04/schema#",
32
"properties": {
43
"discriminators": {
54
"items": {

experimental/apis/data/v0alpha1/query.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,10 @@ type DataSourceRef struct {
443443
// TimeRange represents a time range for a query and is a property of DataQuery.
444444
type TimeRange struct {
445445
// From is the start time of the query.
446-
From string `json:"from" jsonschema:"example=now-1h,default=now-6h"`
446+
From string `json:"from" jsonschema:"default=now-6h"`
447447

448448
// To is the end time of the query.
449-
To string `json:"to" jsonschema:"example=now,default=now"`
449+
To string `json:"to" jsonschema:"default=now"`
450450
}
451451

452452
// ResultAssertions define the expected response shape and query behavior. This is useful to

experimental/apis/data/v0alpha1/query.schema.json

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{
2-
"$schema": "https://json-schema.org/draft-04/schema#",
32
"properties": {
43
"refId": {
54
"type": "string",
@@ -51,18 +50,12 @@
5150
"from": {
5251
"type": "string",
5352
"description": "From is the start time of the query.",
54-
"default": "now-6h",
55-
"examples": [
56-
"now-1h"
57-
]
53+
"default": "now-6h"
5854
},
5955
"to": {
6056
"type": "string",
6157
"description": "To is the end time of the query.",
62-
"default": "now",
63-
"examples": [
64-
"now"
65-
]
58+
"default": "now"
6659
}
6760
},
6861
"additionalProperties": false,

experimental/schemabuilder/example/query.panel.schema.json

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,12 @@
114114
"from": {
115115
"description": "From is the start time of the query.",
116116
"type": "string",
117-
"default": "now-6h",
118-
"examples": [
119-
"now-1h"
120-
]
117+
"default": "now-6h"
121118
},
122119
"to": {
123120
"description": "To is the end time of the query.",
124121
"type": "string",
125-
"default": "now",
126-
"examples": [
127-
"now"
128-
]
122+
"default": "now"
129123
}
130124
},
131125
"additionalProperties": false
@@ -277,18 +271,12 @@
277271
"from": {
278272
"description": "From is the start time of the query.",
279273
"type": "string",
280-
"default": "now-6h",
281-
"examples": [
282-
"now-1h"
283-
]
274+
"default": "now-6h"
284275
},
285276
"to": {
286277
"description": "To is the end time of the query.",
287278
"type": "string",
288-
"default": "now",
289-
"examples": [
290-
"now"
291-
]
279+
"default": "now"
292280
}
293281
},
294282
"additionalProperties": false
@@ -410,18 +398,12 @@
410398
"from": {
411399
"description": "From is the start time of the query.",
412400
"type": "string",
413-
"default": "now-6h",
414-
"examples": [
415-
"now-1h"
416-
]
401+
"default": "now-6h"
417402
},
418403
"to": {
419404
"description": "To is the end time of the query.",
420405
"type": "string",
421-
"default": "now",
422-
"examples": [
423-
"now"
424-
]
406+
"default": "now"
425407
}
426408
},
427409
"additionalProperties": false

experimental/schemabuilder/example/query.request.schema.json

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -132,18 +132,12 @@
132132
"from": {
133133
"description": "From is the start time of the query.",
134134
"type": "string",
135-
"default": "now-6h",
136-
"examples": [
137-
"now-1h"
138-
]
135+
"default": "now-6h"
139136
},
140137
"to": {
141138
"description": "To is the end time of the query.",
142139
"type": "string",
143-
"default": "now",
144-
"examples": [
145-
"now"
146-
]
140+
"default": "now"
147141
}
148142
},
149143
"additionalProperties": false
@@ -303,18 +297,12 @@
303297
"from": {
304298
"description": "From is the start time of the query.",
305299
"type": "string",
306-
"default": "now-6h",
307-
"examples": [
308-
"now-1h"
309-
]
300+
"default": "now-6h"
310301
},
311302
"to": {
312303
"description": "To is the end time of the query.",
313304
"type": "string",
314-
"default": "now",
315-
"examples": [
316-
"now"
317-
]
305+
"default": "now"
318306
}
319307
},
320308
"additionalProperties": false
@@ -444,18 +432,12 @@
444432
"from": {
445433
"description": "From is the start time of the query.",
446434
"type": "string",
447-
"default": "now-6h",
448-
"examples": [
449-
"now-1h"
450-
]
435+
"default": "now-6h"
451436
},
452437
"to": {
453438
"description": "To is the end time of the query.",
454439
"type": "string",
455-
"default": "now",
456-
"examples": [
457-
"now"
458-
]
440+
"default": "now"
459441
}
460442
},
461443
"additionalProperties": false

experimental/schemabuilder/reflector_test.go

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package schemabuilder
22

33
import (
4+
"encoding/json"
45
"os"
56
"reflect"
67
"testing"
78

9+
"github.com/go-openapi/loads"
10+
"github.com/go-openapi/spec"
11+
"github.com/go-openapi/strfmt"
12+
"github.com/go-openapi/validate"
813
"github.com/grafana/grafana-plugin-sdk-go/data"
914
apisdata "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
1015
"github.com/invopop/jsonschema"
@@ -32,8 +37,9 @@ func TestWriteQuerySchema(t *testing.T) {
3237

3338
query := builder.reflector.Reflect(&apisdata.CommonQueryProperties{})
3439
updateEnumDescriptions(query)
40+
41+
query.Version = "" // $schema is not allowed in openapi v2's SchemaObject
3542
query.ID = ""
36-
query.Version = draft04 // used by kube-openapi
3743
query.Description = "Generic query properties"
3844
query.AdditionalProperties = jsonschema.TrueSchema
3945

@@ -49,15 +55,74 @@ func TestWriteQuerySchema(t *testing.T) {
4955
require.NoError(t, err)
5056
require.Equal(t, 8, len(schema.Properties))
5157

58+
bytes, err := os.ReadFile(outfile)
59+
require.NoError(t, err)
60+
validateOpenAPIv2Schema(t, bytes, outfile)
61+
5262
// Add schema for query type definition
5363
query = builder.reflector.Reflect(&apisdata.QueryTypeDefinitionSpec{})
5464
updateEnumDescriptions(query)
5565
query.ID = ""
56-
query.Version = draft04 // used by kube-openapi
66+
query.Version = "" // $schema is not allowed in openapi v2's SchemaObject
5767
outfile = "../apis/data/v0alpha1/query.definition.schema.json"
5868
old, _ = os.ReadFile(outfile)
5969
maybeUpdateFile(t, outfile, query, old)
6070

71+
bytes, err = os.ReadFile(outfile)
72+
require.NoError(t, err)
73+
validateOpenAPIv2Schema(t, bytes, outfile)
74+
6175
def := apisdata.GetOpenAPIDefinitions(nil)["github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.QueryTypeDefinitionSpec"]
6276
require.Equal(t, query.Properties.Len(), len(def.Schema.Properties))
6377
}
78+
79+
func validateOpenAPIv2Schema(t *testing.T, data []byte, file string) {
80+
t.Helper()
81+
// --- Stage 1: Check for disallowed top-level keys ---
82+
// https://github.com/go-openapi/spec/blob/0201d0c/schema.go#L622 json.Unmarshal on `spec.Schema` gets rid of $schema - so need to unmarshall into a generic map
83+
var genericMap map[string]interface{}
84+
if err := json.Unmarshal(data, &genericMap); err != nil {
85+
require.NoError(t, err, file)
86+
}
87+
88+
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject doesn't contain $schema
89+
if _, found := genericMap["$schema"]; found {
90+
require.Fail(t, "$schema not allowed", file)
91+
}
92+
93+
// --- Stage 2: Validate against OpenAPI v2 structure using go-openapi ---
94+
95+
// 2a. Try unmarshal the input data into a spec.Schema.
96+
var schema spec.Schema
97+
if err := json.Unmarshal(data, &schema); err != nil {
98+
require.NoError(t, err, file)
99+
}
100+
101+
// 2b. Create minimal valid swagger spec & marshal it to bytes.
102+
swaggerBytes, err := json.Marshal(&spec.Swagger{
103+
SwaggerProps: spec.SwaggerProps{
104+
Swagger: "2.0",
105+
Info: &spec.Info{
106+
InfoProps: spec.InfoProps{
107+
Title: "example", // Placeholder, required
108+
Version: "1.0", // Placeholder, required
109+
},
110+
},
111+
Paths: &spec.Paths{ // Required, can be empty
112+
Paths: map[string]spec.PathItem{},
113+
},
114+
Definitions: spec.Definitions{
115+
"SchemaToValidate": schema,
116+
},
117+
},
118+
})
119+
require.NoError(t, err, file)
120+
121+
// 2c. Load the spec structure using loads.Analyzed.
122+
doc, err := loads.Analyzed(swaggerBytes, "2.0")
123+
require.NoError(t, err)
124+
125+
// 2d. Validate the loaded document against the official OpenAPI 2.0 meta-schema.
126+
err = validate.Spec(doc, strfmt.Default)
127+
require.NoError(t, err, file)
128+
}

go.mod

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ require (
88
github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2
99
github.com/elazarl/goproxy v1.7.2
1010
github.com/getkin/kin-openapi v0.131.0
11+
github.com/go-openapi/loads v0.22.0
12+
github.com/go-openapi/spec v0.21.0
13+
github.com/go-openapi/strfmt v0.23.0
14+
github.com/go-openapi/validate v0.24.0
1115
github.com/google/go-cmp v0.7.0
1216
github.com/google/uuid v1.6.0
1317
github.com/grafana/otel-profiling-go v0.5.1
@@ -43,16 +47,15 @@ require (
4347
golang.org/x/sync v0.13.0
4448
golang.org/x/sys v0.32.0
4549
golang.org/x/text v0.24.0
50+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a
4651
google.golang.org/grpc v1.71.1
4752
google.golang.org/protobuf v1.36.6
48-
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // @grafana/grafana-app-platform-squad
53+
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // @grafana/grafana-app-platform-squad
4954
)
5055

51-
require google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a
52-
5356
require (
5457
github.com/BurntSushi/toml v1.4.0 // indirect
55-
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
58+
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
5659
github.com/bahlo/generic-list-go v0.2.0 // indirect
5760
github.com/beorn7/perks v1.0.1 // indirect
5861
github.com/buger/jsonparser v1.1.1 // indirect
@@ -64,15 +67,16 @@ require (
6467
github.com/fatih/color v1.15.0 // indirect
6568
github.com/go-logr/logr v1.4.2 // indirect
6669
github.com/go-logr/stdr v1.2.2 // indirect
70+
github.com/go-openapi/analysis v0.23.0 // indirect
71+
github.com/go-openapi/errors v0.22.0 // indirect
6772
github.com/go-openapi/jsonpointer v0.21.0 // indirect
68-
github.com/go-openapi/jsonreference v0.20.1 // indirect
73+
github.com/go-openapi/jsonreference v0.21.0 // indirect
6974
github.com/go-openapi/swag v0.23.0 // indirect
7075
github.com/goccy/go-json v0.10.5 // indirect
7176
github.com/gogo/protobuf v1.3.2 // indirect
7277
github.com/golang/protobuf v1.5.4 // indirect
7378
github.com/google/flatbuffers v25.2.10+incompatible // indirect
74-
github.com/google/gnostic-models v0.6.8 // indirect
75-
github.com/google/gofuzz v1.2.0 // indirect
79+
github.com/google/gnostic-models v0.6.9 // indirect
7680
github.com/gorilla/mux v1.8.0 // indirect
7781
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
7882
github.com/hashicorp/yamux v0.1.1 // indirect
@@ -83,13 +87,15 @@ require (
8387
github.com/mattn/go-colorable v0.1.13 // indirect
8488
github.com/mattn/go-isatty v0.0.20 // indirect
8589
github.com/mattn/go-runewidth v0.0.16 // indirect
90+
github.com/mitchellh/mapstructure v1.5.0 // indirect
8691
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
8792
github.com/modern-go/reflect2 v1.0.2 // indirect
8893
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
8994
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
9095
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
9196
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
9297
github.com/oklog/run v1.0.0 // indirect
98+
github.com/oklog/ulid v1.3.1 // indirect
9399
github.com/perimeterx/marshmallow v1.1.5 // indirect
94100
github.com/pierrec/lz4/v4 v4.1.22 // indirect
95101
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -101,6 +107,7 @@ require (
101107
github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 // indirect
102108
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
103109
github.com/zeebo/xxh3 v1.0.2 // indirect
110+
go.mongodb.org/mongo-driver v1.14.0 // indirect
104111
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
105112
go.opentelemetry.io/otel/metric v1.35.0 // indirect
106113
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
@@ -111,4 +118,5 @@ require (
111118
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
112119
gopkg.in/yaml.v3 v3.0.1 // indirect
113120
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
121+
sigs.k8s.io/randfill v1.0.0 // indirect
114122
)

0 commit comments

Comments
 (0)