Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Schema examples annotation #580

Merged
merged 13 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
192 changes: 192 additions & 0 deletions pkg/cmd/template/schema_author_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,198 @@ schema.yml:
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertFails(t, filesToProcess, expectedErr, opts)
})
})
t.Run("when schema/examples annotation value", func(t *testing.T) {
t.Run("is empty", func(t *testing.T) {
schemaYAML := `#@data/values-schema
---
#@schema/examples
key: val
`
expectedErr := `
Invalid schema
==============

syntax error in @schema/examples annotation
schema.yml:
|
3 | #@schema/examples
4 | key: val
|

= found: missing value in @schema/examples (by schema.yml:3)
= expected: tuple with description and example
gcheadle-vmware marked this conversation as resolved.
Show resolved Hide resolved
`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertFails(t, filesToProcess, expectedErr, opts)
})
t.Run("is not a tuple", func(t *testing.T) {
schemaYAML := `#@data/values-schema
---
#@schema/examples "example value"
key: val
`
expectedErr := `
Invalid schema
==============

syntax error in @schema/examples annotation
schema.yml:
|
3 | #@schema/examples "example value"
4 | key: val
|

= found: non tuple in @schema/examples (by schema.yml:3)
gcheadle-vmware marked this conversation as resolved.
Show resolved Hide resolved
= expected: tuple with optional string description and required example value
= hint: use a trailing comma to construct tuple with a single value. e.g. ('example value',)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this would be a non-empty description with a None value...?

I think you mean to hint: (, "example value")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I looked into this a bit so im commenting what I found) I thought ('example value',) was odd too. But I believe the trailing comma is the correct syntax. That makes it a single-element tuple. I believe the leading comma syntax isn't supported.
https://wiki.python.org/moin/TupleSyntax#:~:text=Multiple%20Element%20Tuples&text=Multiple%2Delement%20tuples%20may%20be,be%20enclosed%20in%20parentheses%2C%20e.g.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below about disallowing this for now.

`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertFails(t, filesToProcess, expectedErr, opts)
})
t.Run("is an empty tuple", func(t *testing.T) {
schemaYAML := `#@data/values-schema
---
#@schema/examples ()
key: val
`
expectedErr := `
Invalid schema
==============

syntax error in @schema/examples annotation
schema.yml:
|
3 | #@schema/examples ()
4 | key: val
|

= found: empty tuple in @schema/examples (by schema.yml:3)
= expected: tuple with optional string description and required example value
gcheadle-vmware marked this conversation as resolved.
Show resolved Hide resolved
`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertFails(t, filesToProcess, expectedErr, opts)
})
t.Run("has more than two args in an example", func(t *testing.T) {
schemaYAML := `#@data/values-schema
---
#@schema/examples ("key example", "val", "value")
key: val
`
expectedErr := `
Invalid schema
==============

syntax error in @schema/examples annotation
schema.yml:
|
3 | #@schema/examples ("key example", "val", "value")
4 | key: val
|

= found: 3 arguments in @schema/examples (by schema.yml:3)
= expected: no more than 2 arguments per tuple. e.g. @schema/examples ('description', exampleValue())
`
gcheadle-vmware marked this conversation as resolved.
Show resolved Hide resolved

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertFails(t, filesToProcess, expectedErr, opts)
})
t.Run("example description is not a string", func(t *testing.T) {
schemaYAML := `#@data/values-schema
---
#@schema/examples (3.14, 5)
key: 7.3
`
expectedErr := `
Invalid schema
==============

syntax error in @schema/examples annotation
schema.yml:
|
3 | #@schema/examples (3.14, 5)
4 | key: 7.3
|

= found: Non-string value in @schema/examples (by schema.yml:3)
= expected: string
gcheadle-vmware marked this conversation as resolved.
Show resolved Hide resolved
= hint: @schema/examples optionally accepts a string description as the first argument in a tuple
`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertFails(t, filesToProcess, expectedErr, opts)
})
t.Run("is key=value pair", func(t *testing.T) {
schemaYAML := `#@data/values-schema
---
#@schema/examples key=True
key: val
`
expectedErr := `
Invalid schema
==============

syntax error in @schema/examples annotation
schema.yml:
|
3 | #@schema/examples key=True
4 | key: val
|

= found: keyword argument in @schema/examples (by schema.yml:3)
= expected: tuple with description and example
`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertFails(t, filesToProcess, expectedErr, opts)
})
t.Run("does not match type of annotated node", func(t *testing.T) {
schemaYAML := `#@data/values-schema
---
#@schema/examples ("Zero value", 0)
enabled: false
`
expectedErr := `Invalid schema - @schema/examples has wrong type
================================================

schema.yml:
|
3 | #@schema/examples ("Zero value", 0)
4 | enabled: false
|

= found: integer (by schema.yml:3)
= expected: boolean (by schema.yml:4)
= hint: is the default value set using @schema/default?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just note: the hint is misplaced and is fixed in #582. This is a new instance of the hint and will need to be removed after that PR is merged.

`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertFails(t, filesToProcess, expectedErr, opts)
})
})
Expand Down
120 changes: 111 additions & 9 deletions pkg/cmd/template/schema_inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ components:
})

}

func TestSchemaInspect_annotation_adds_key(t *testing.T) {
t.Run("when description provided by @schema/desc", func(t *testing.T) {
opts := cmdtpl.NewOptions()
Expand Down Expand Up @@ -578,40 +579,40 @@ paths: {}
components:
schemas:
dataValues:
description: Network configuration values
type: object
additionalProperties: false
description: Network configuration values
properties:
db_conn:
type: array
description: List of database connections
type: array
items:
description: A network entry
type: object
additionalProperties: false
description: A network entry
properties:
hostname:
description: The hostname
type: string
default: ""
description: The hostname
port:
description: Port should be between 49152 through 65535
type: integer
default: 0
description: Port should be between 49152 through 65535
timeout:
description: Timeout in minutes
type: number
default: 1
format: float
description: Timeout in minutes
any_key:
description: Any type is allowed
nullable: true
default: thing
description: Any type is allowed
null_key:
description: When not provided, the default is null
type: string
default: null
nullable: true
description: When not provided, the default is null
default: []
`

Expand Down Expand Up @@ -685,16 +686,117 @@ components:
nullable: true
default: thing
null_key:
title: When not provided, the default is null
type: string
default: null
title: When not provided, the default is null
nullable: true
default: []
`
filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertSucceedsDocSet(t, filesToProcess, expected, opts)
})
t.Run("when examples are provided by @schema/examples", func(t *testing.T) {
opts := cmdtpl.NewOptions()
opts.DataValuesFlags.InspectSchema = true
opts.RegularFilesSourceOpts.OutputType.Types = []string{"openapi-v3"}

schemaYAML := `#@data/values-schema
#@schema/examples ("schema example description", {"db_conn": [{"hostname": "localhost", "port": 8080, "timeout": 4.2, "any_key": "anything", "null_key": None}]})
---
#@schema/examples ("db_conn example description", [{"hostname": "localhost", "port": 8080, "timeout": 4.2, "any_key": "anything", "null_key": None}])
db_conn:
#@schema/examples ("db_conn array example description", {"hostname": "localhost", "port": 8080, "timeout": 4.2, "any_key": "anything", "null_key": "not null"})
-
#@schema/examples ("hostname example description", "localhost")
#@schema/desc "The hostname"
hostname: ""
#@schema/examples (8080,)
port: 0
#@schema/examples ("timeout example description", 4.2), ("another timeout ex desc", 5)
timeout: 1.0
#@schema/examples ("any_key example description", "anything")
#@schema/type any=True
any_key: thing
#@schema/examples ("null_key example description", "anything")
#@schema/nullable
null_key: ""
`
expected := `openapi: 3.0.0
info:
version: 0.1.0
title: Schema for data values, generated by ytt
paths: {}
components:
schemas:
dataValues:
x-example-description: schema example description
example:
db_conn:
- hostname: localhost
port: 8080
timeout: 4.2
any_key: anything
null_key: null
type: object
additionalProperties: false
properties:
db_conn:
x-example-description: db_conn example description
example:
- hostname: localhost
port: 8080
timeout: 4.2
any_key: anything
null_key: null
type: array
items:
x-example-description: db_conn array example description
example:
hostname: localhost
port: 8080
timeout: 4.2
any_key: anything
null_key: not null
type: object
additionalProperties: false
properties:
hostname:
description: The hostname
x-example-description: hostname example description
example: localhost
type: string
default: ""
port:
example: 8080
type: integer
default: 0
timeout:
x-example-description: timeout example description
example: 4.2
type: number
default: 1
format: float
any_key:
x-example-description: any_key example description
example: anything
nullable: true
default: thing
null_key:
x-example-description: null_key example description
example: anything
type: string
default: null
nullable: true
default: []
`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(schemaYAML))),
})

assertSucceedsDocSet(t, filesToProcess, expected, opts)
})
}
Expand Down