Skip to content

Commit 6e76280

Browse files
CLOUDP-319370: Add curl and atlascli samples to the OAS (#722)
1 parent daec2a5 commit 6e76280

18 files changed

+61833
-491
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package filter
16+
17+
import (
18+
"time"
19+
20+
"github.com/getkin/kin-openapi/openapi3"
21+
"github.com/mongodb/openapi/tools/cli/internal/apiversion"
22+
)
23+
24+
const codeSampleExtensionName = "x-codeSamples"
25+
26+
// https://redocly.com/docs-legacy/api-reference-docs/specification-extensions/x-code-samples#x-codesamples
27+
type codeSample struct {
28+
Lang string `json:"lang,omitempty" yaml:"lang,omitempty"`
29+
Label string `json:"label,omitempty" yaml:"label,omitempty"`
30+
Source string `json:"source,omitempty" yaml:"source,omitempty"`
31+
}
32+
33+
// CodeSampleFilter modifies includes the fields "x-state" and "x-beta" to the "preview" and "upcoming" APIs Operations.
34+
// The "x-state" and "x-beta" fields are bump.sh custom fields to include budges
35+
// Bump.sh feature: https://docs.bump.sh/help/specification-support/doc-code-samples/#example-usage
36+
type CodeSampleFilter struct {
37+
oas *openapi3.T
38+
metadata *Metadata
39+
}
40+
41+
func (f *CodeSampleFilter) ValidateMetadata() error {
42+
return validateMetadataWithVersion(f.metadata)
43+
}
44+
45+
func (f *CodeSampleFilter) Apply() error {
46+
for pathName, p := range f.oas.Paths.Map() {
47+
for opMethod, op := range p.Operations() {
48+
if err := f.includeCodeSamplesForOperation(pathName, opMethod, op); err != nil {
49+
return err
50+
}
51+
}
52+
}
53+
54+
return nil
55+
}
56+
57+
func (f *CodeSampleFilter) newCurlCodeSamplesForOperation(pathName, opMethod string) codeSample {
58+
source := "curl --user \"{PUBLIC-KEY}:{PRIVATE-KEY}\" \\\n --digest \\\n " +
59+
"--header \"Accept: application/vnd.atlas." + apiVersion(f.metadata.targetVersion) + "+json\" \\\n "
60+
61+
switch opMethod {
62+
case "GET":
63+
source += "-X " + opMethod + " \"" + pathName + "?pretty=true\""
64+
case "DELETE":
65+
source += "-X " + opMethod + " \"" + pathName + "\""
66+
case "POST", "PATCH", "PUT":
67+
source += "-X " + opMethod + " \"" + pathName + "\"\n "
68+
source += "-d " + "{ <Payload> }"
69+
}
70+
71+
return codeSample{
72+
Lang: "cURL",
73+
Label: "curl",
74+
Source: source,
75+
}
76+
}
77+
78+
func apiVersion(version *apiversion.APIVersion) string {
79+
if version.IsStable() {
80+
return version.Date().Format(time.DateOnly)
81+
}
82+
83+
if version.IsPreview() {
84+
return "preview"
85+
}
86+
87+
// Upcoming api version
88+
return version.Date().Format(time.DateOnly) + ".upcoming"
89+
}
90+
91+
func newAtlasCliCodeSamplesForOperation(op *openapi3.Operation) codeSample {
92+
return codeSample{
93+
Lang: "cURL",
94+
Label: "Atlas CLI",
95+
Source: "atlas api " + op.OperationID + " --help",
96+
}
97+
}
98+
99+
func (f *CodeSampleFilter) includeCodeSamplesForOperation(pathName, opMethod string, op *openapi3.Operation) error {
100+
if op == nil || opMethod == "" || pathName == "" {
101+
return nil
102+
}
103+
104+
if op.Extensions == nil {
105+
op.Extensions = map[string]any{}
106+
}
107+
108+
op.Extensions[codeSampleExtensionName] = []codeSample{
109+
f.newCurlCodeSamplesForOperation(pathName, opMethod),
110+
newAtlasCliCodeSamplesForOperation(op),
111+
}
112+
return nil
113+
}
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package filter
16+
17+
import (
18+
"reflect"
19+
"testing"
20+
21+
"github.com/getkin/kin-openapi/openapi3"
22+
"github.com/mongodb/openapi/tools/cli/internal/apiversion"
23+
"github.com/stretchr/testify/require"
24+
)
25+
26+
func TestCodeSampleFilter(t *testing.T) {
27+
testCases := []struct {
28+
name string
29+
oas *openapi3.T
30+
version string
31+
expectedOas *openapi3.T
32+
}{
33+
{
34+
name: "stable api",
35+
version: "2025-01-01",
36+
oas: &openapi3.T{
37+
Paths: openapi3.NewPaths(openapi3.WithPath("test", &openapi3.PathItem{
38+
Get: &openapi3.Operation{
39+
OperationID: "testOperationID",
40+
Summary: "testSummary",
41+
Responses: openapi3.NewResponses(openapi3.WithName("200", &openapi3.Response{
42+
Content: openapi3.Content{
43+
"application/vnd.atlas.2025-01-01+json": {
44+
Schema: &openapi3.SchemaRef{
45+
Ref: "#/components/schemas/PaginatedAppUserView",
46+
},
47+
Extensions: map[string]any{
48+
"x-gen-version": "2025-01-01",
49+
},
50+
},
51+
},
52+
})),
53+
Extensions: map[string]any{
54+
"x-sunset": "9999-12-31",
55+
},
56+
},
57+
})),
58+
},
59+
expectedOas: &openapi3.T{
60+
Paths: openapi3.NewPaths(openapi3.WithPath("test", &openapi3.PathItem{
61+
Get: &openapi3.Operation{
62+
OperationID: "testOperationID",
63+
Summary: "testSummary",
64+
Responses: openapi3.NewResponses(openapi3.WithName("200", &openapi3.Response{
65+
Content: openapi3.Content{
66+
"application/vnd.atlas.2025-01-01+json": {
67+
Schema: &openapi3.SchemaRef{
68+
Ref: "#/components/schemas/PaginatedAppUserView",
69+
},
70+
Extensions: map[string]any{
71+
"x-gen-version": "2025-01-01",
72+
},
73+
},
74+
},
75+
})),
76+
Extensions: map[string]any{
77+
"x-sunset": "9999-12-31",
78+
"x-codeSamples": []codeSample{
79+
{
80+
Lang: "cURL",
81+
Label: "curl",
82+
Source: "curl --user \"{PUBLIC-KEY}:{PRIVATE-KEY}\" \\\n --digest \\\n " +
83+
"--header \"Accept: application/vnd.atlas.2025-01-01+json\" \\\n " + "-X GET \"test?pretty=true\"",
84+
},
85+
{
86+
Lang: "cURL",
87+
Label: "Atlas CLI",
88+
Source: "atlas api testOperationID --help",
89+
},
90+
},
91+
},
92+
},
93+
})),
94+
},
95+
},
96+
{
97+
name: "preview api",
98+
version: "preview",
99+
oas: &openapi3.T{
100+
Paths: openapi3.NewPaths(openapi3.WithPath("test", &openapi3.PathItem{
101+
Get: &openapi3.Operation{
102+
OperationID: "testOperationID",
103+
Summary: "testSummary",
104+
Responses: openapi3.NewResponses(openapi3.WithName("200", &openapi3.Response{
105+
Content: openapi3.Content{
106+
"application/vnd.atlas.preview+json": {
107+
Schema: &openapi3.SchemaRef{
108+
Ref: "#/components/schemas/PaginatedAppUserView",
109+
},
110+
Extensions: map[string]any{
111+
"x-gen-version": "preview",
112+
},
113+
},
114+
},
115+
})),
116+
Extensions: map[string]any{
117+
"x-sunset": "9999-12-31",
118+
},
119+
},
120+
})),
121+
},
122+
expectedOas: &openapi3.T{
123+
Paths: openapi3.NewPaths(openapi3.WithPath("test", &openapi3.PathItem{
124+
Get: &openapi3.Operation{
125+
OperationID: "testOperationID",
126+
Summary: "testSummary",
127+
Responses: openapi3.NewResponses(openapi3.WithName("200", &openapi3.Response{
128+
Content: openapi3.Content{
129+
"application/vnd.atlas.preview+json": {
130+
Schema: &openapi3.SchemaRef{
131+
Ref: "#/components/schemas/PaginatedAppUserView",
132+
},
133+
Extensions: map[string]any{
134+
"x-gen-version": "preview",
135+
},
136+
},
137+
},
138+
})),
139+
Extensions: map[string]any{
140+
"x-sunset": "9999-12-31",
141+
"x-codeSamples": []codeSample{
142+
{
143+
Lang: "cURL",
144+
Label: "curl",
145+
Source: "curl --user \"{PUBLIC-KEY}:{PRIVATE-KEY}\" \\\n --digest \\\n " +
146+
"--header \"Accept: application/vnd.atlas.preview+json\" \\\n " + "-X GET \"test?pretty=true\"",
147+
},
148+
{
149+
Lang: "cURL",
150+
Label: "Atlas CLI",
151+
Source: "atlas api testOperationID --help",
152+
},
153+
},
154+
},
155+
},
156+
})),
157+
},
158+
},
159+
{
160+
name: "upcoming api",
161+
version: "2025-01-01.upcoming",
162+
oas: &openapi3.T{
163+
Paths: openapi3.NewPaths(openapi3.WithPath("test", &openapi3.PathItem{
164+
Get: &openapi3.Operation{
165+
OperationID: "testOperationID",
166+
Summary: "testSummary",
167+
Responses: openapi3.NewResponses(openapi3.WithName("200", &openapi3.Response{
168+
Content: openapi3.Content{
169+
"application/vnd.atlas.2025-01-01.upcoming+json": {
170+
Schema: &openapi3.SchemaRef{
171+
Ref: "#/components/schemas/PaginatedAppUserView",
172+
},
173+
Extensions: map[string]any{
174+
"x-gen-version": "2025-01-01.upcoming",
175+
},
176+
},
177+
},
178+
})),
179+
Extensions: map[string]any{
180+
"x-sunset": "9999-12-31",
181+
},
182+
},
183+
})),
184+
},
185+
expectedOas: &openapi3.T{
186+
Paths: openapi3.NewPaths(openapi3.WithPath("test", &openapi3.PathItem{
187+
Get: &openapi3.Operation{
188+
OperationID: "testOperationID",
189+
Summary: "testSummary",
190+
Responses: openapi3.NewResponses(openapi3.WithName("200", &openapi3.Response{
191+
Content: openapi3.Content{
192+
"application/vnd.atlas.2025-01-01.upcoming+json": {
193+
Schema: &openapi3.SchemaRef{
194+
Ref: "#/components/schemas/PaginatedAppUserView",
195+
},
196+
Extensions: map[string]any{
197+
"x-gen-version": "2025-01-01.upcoming",
198+
},
199+
},
200+
},
201+
})),
202+
Extensions: map[string]any{
203+
"x-sunset": "9999-12-31",
204+
"x-codeSamples": []codeSample{
205+
{
206+
Lang: "cURL",
207+
Label: "curl",
208+
Source: "curl --user \"{PUBLIC-KEY}:{PRIVATE-KEY}\" \\\n --digest \\\n " +
209+
"--header \"Accept: application/vnd.atlas.2025-01-01.upcoming+json\" \\\n " + "-X GET \"test?pretty=true\"",
210+
},
211+
{
212+
Lang: "cURL",
213+
Label: "Atlas CLI",
214+
Source: "atlas api testOperationID --help",
215+
},
216+
},
217+
},
218+
},
219+
})),
220+
},
221+
},
222+
}
223+
224+
for _, tt := range testCases {
225+
t.Run(tt.name, func(t *testing.T) {
226+
t.Parallel()
227+
oas := tt.oas
228+
version, err := apiversion.New(apiversion.WithVersion(tt.version))
229+
require.NoError(t, err)
230+
231+
filter := &CodeSampleFilter{
232+
oas: oas,
233+
metadata: &Metadata{targetVersion: version, targetEnv: "dev"},
234+
}
235+
236+
require.NoError(t, filter.Apply())
237+
if !reflect.DeepEqual(tt.expectedOas, tt.oas) {
238+
t.Errorf("expected %v, got %v", tt.expectedOas, oas)
239+
}
240+
})
241+
}
242+
}

tools/cli/internal/openapi/filter/filter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func DefaultFilters(oas *openapi3.T, metadata *Metadata) []Filter {
8181
&SunsetFilter{oas: oas},
8282
&SchemasFilter{oas: oas},
8383
&BumpFilter{oas: oas, metadata: metadata},
84+
&CodeSampleFilter{oas: oas, metadata: metadata},
8485
}
8586
}
8687

tools/cli/internal/openapi/filter/filter_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func TestDefaultFilters(t *testing.T) {
112112
metadata := &Metadata{}
113113
filters := DefaultFilters(doc, metadata)
114114

115-
assert.Len(t, filters, 10)
115+
assert.Len(t, filters, 11)
116116
}
117117

118118
func TestFiltersWithoutVersioning(t *testing.T) {

0 commit comments

Comments
 (0)