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

Support providing examples for custom types #73

Merged
merged 1 commit into from
Feb 11, 2022

Conversation

nikicc
Copy link
Contributor

@nikicc nikicc commented Feb 7, 2022

Fixes #72.

@nikicc nikicc force-pushed the support-custom-type-examples branch 2 times, most recently from 7080bcb to ba321bc Compare February 7, 2022 17:23
@codecov
Copy link

codecov bot commented Feb 7, 2022

Codecov Report

Merging #73 (10fdc74) into master (ad0868d) will decrease coverage by 0.18%.
The diff coverage is 60.00%.

❗ Current head 10fdc74 differs from pull request most recent head 815e4ae. Consider uploading reports for the commit 815e4ae to get more accurate results

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #73      +/-   ##
==========================================
- Coverage   95.19%   95.01%   -0.19%     
==========================================
  Files           7        7              
  Lines         957      962       +5     
==========================================
+ Hits          911      914       +3     
- Misses         30       32       +2     
  Partials       16       16              
Impacted Files Coverage Δ
openapi/types.go 100.00% <ø> (ø)
openapi/generator.go 94.00% <60.00%> (-0.27%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ad0868d...815e4ae. Read the comment docs.

@@ -1235,6 +1235,10 @@ func fieldNameFromTag(sf reflect.StructField, tagName string) string {
return name
}

type OpenAPIExample interface {
Copy link
Owner

@wI2L wI2L Feb 7, 2022

Choose a reason for hiding this comment

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

Just thought that this interface might be expanded in the future for others features. Perhaps we should take that opportunity to rename it CustomType, or something else (if you have a better idead, please share) and aggregate all methods within it.

Also, exported type needs a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed; CustomType seems more general and easier to use for other stuff later if needed.

Added a comment & renamed to CustomType

@@ -1276,6 +1280,13 @@ func parseExampleValue(t reflect.Type, value string) (interface{}, error) {
return strconv.ParseFloat(value, t.Bits())
case reflect.Ptr:
return parseExampleValue(t.Elem(), value)
case reflect.Struct:
// we only support structs that implement OpenAPIExample interface
casted, ok := reflect.New(t).Interface().(OpenAPIExample)
Copy link
Owner

Choose a reason for hiding this comment

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

s/casted/i

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

README.md Outdated
To be able to provide examples for structs, they must implement the following interface:
```go
type OpenAPIExample interface {
ParseExample(v string) interface{}
Copy link
Owner

Choose a reason for hiding this comment

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

The method should be able to return an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Added an error now.

// we only support structs that implement OpenAPIExample interface
casted, ok := reflect.New(t).Interface().(OpenAPIExample)
if ok {
return casted.ParseExample(value), nil
Copy link
Owner

Choose a reason for hiding this comment

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

return i.ParseExample(value)
We should return the error returned by the implem.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

if ok {
return casted.ParseExample(value), nil
}
return nil, fmt.Errorf(" structs (type %s) that do not implement OpenAPIExample are not supported", t.String())
Copy link
Owner

Choose a reason for hiding this comment

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

I suggest: "type %s doesn not implement {interface name}"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -733,6 +734,13 @@ func TestSetServers(t *testing.T) {
assert.Equal(t, servers, g.API().Servers)
}

type CustomUnit struct {
Copy link
Owner

Choose a reason for hiding this comment

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

Could you add a test for a known case, such as your time.Time ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Happy to. Added.

@@ -733,6 +734,13 @@ func TestSetServers(t *testing.T) {
assert.Equal(t, servers, g.API().Servers)
}

type CustomUnit struct {
Copy link
Owner

Choose a reason for hiding this comment

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

Doesn't need to be exported: s/CustomUnit/customUnit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed for both the existing type and the one I just aded for time.Time.

@wI2L
Copy link
Owner

wI2L commented Feb 7, 2022

Thanks for the PR.
I added a few comments to ammend some parts of the implementation proposal that I wrote in the issue after thinking about it properly.

Copy link
Contributor Author

@nikicc nikicc left a comment

Choose a reason for hiding this comment

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

@wI2L pushed the fixes. Ready for another pass 👀

README.md Outdated
To be able to provide examples for structs, they must implement the following interface:
```go
type OpenAPIExample interface {
ParseExample(v string) interface{}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Added an error now.

@@ -1235,6 +1235,10 @@ func fieldNameFromTag(sf reflect.StructField, tagName string) string {
return name
}

type OpenAPIExample interface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed; CustomType seems more general and easier to use for other stuff later if needed.

Added a comment & renamed to CustomType

@@ -1276,6 +1280,13 @@ func parseExampleValue(t reflect.Type, value string) (interface{}, error) {
return strconv.ParseFloat(value, t.Bits())
case reflect.Ptr:
return parseExampleValue(t.Elem(), value)
case reflect.Struct:
// we only support structs that implement OpenAPIExample interface
casted, ok := reflect.New(t).Interface().(OpenAPIExample)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// we only support structs that implement OpenAPIExample interface
casted, ok := reflect.New(t).Interface().(OpenAPIExample)
if ok {
return casted.ParseExample(value), nil
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

if ok {
return casted.ParseExample(value), nil
}
return nil, fmt.Errorf(" structs (type %s) that do not implement OpenAPIExample are not supported", t.String())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -733,6 +734,13 @@ func TestSetServers(t *testing.T) {
assert.Equal(t, servers, g.API().Servers)
}

type CustomUnit struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Happy to. Added.

@@ -733,6 +734,13 @@ func TestSetServers(t *testing.T) {
assert.Equal(t, servers, g.API().Servers)
}

type CustomUnit struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed for both the existing type and the one I just aded for time.Time.

@nikicc nikicc marked this pull request as ready for review February 7, 2022 19:29
@nikicc nikicc requested a review from wI2L February 8, 2022 08:58
type OpenAPIExample interface {
ParseExample(v string) interface{}
// CustomType describes the interface of custom types that can be used in JSON responses.
type CustomType interface {
Copy link
Owner

Choose a reason for hiding this comment

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

Could you move it to fizz/openapi/types.go, with the other types-related interfaces?

Copy link
Owner

Choose a reason for hiding this comment

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

Actually, I think I'm reconsidering my proposal to name that interface CustomType.
We already have two interfaces Type and DataType that are implemented by custom types.

Ideally, this should be one single interface CustomType. Perhaps for a v1.0.

In the meantime, I think we should keep a seperate interface, Exampler (the name sucks a bit, but we usually end interface name with "er". If you have a better name in mind, don't hesitate).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to fizz/openapi/types.go.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the meantime, I think we should keep a seperate interface, Exampler (the name sucks a bit, but we usually end interface name with "er".

Renamed to Exampler.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ideally, this should be one single interface CustomType. Perhaps for a v1.0.

One argument for keeping these separate interfaces separate might be that it's easier to fulfil the interfaces if they are separate (e.g. if I only want to provide examples, currently I only need to implement the ParseExample method, while with one bigger interface I'd need to implement all the methods).

Copy link
Owner

Choose a reason for hiding this comment

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

Yes, you have a valid point.

if err != nil {
return nil, err
}
return t1.Format("2006-01-02T15:04:05"), nil
Copy link
Owner

Choose a reason for hiding this comment

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

I think, for the sake of the example, the return should be t1, and the type should implement a String() and/or MarshalJson methods. The goal is to return the concrete type, and leave the marshaling to the implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, not sure. If we refactor, then we would need something like this:

type customTime struct {
	time.Time
}

func (c customTime) String() string {
	return c.Format("2006-01-02T15:04:05")
}

func (c customTime) ParseExample(v string) (interface{}, error) {
	t1, err := time.Parse(time.RFC3339, v)
	if err != nil {
		return nil, err
	}
	return customTime{t1}, nil
}

but then we can't test is as nicely in TestGenerator_parseExampleValue as the returned value is an object, not a string representation formatted in the the format we like. WDYT?

Copy link
Owner

Choose a reason for hiding this comment

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

Yes, that's what I thought of in term of implem.
The marshaling/string representation of the custom type can be tested by comparing the output spec, and if we want to add a test of the type-specific ParseExample method, we just need to ensure that the returned value has the proper type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored now.

"15",
"15 USD",
},
{
"mapping pointers to custom structs",
reflect.PtrTo(reflect.TypeOf(CustomUnit{})),
"mapping pointers to customUnit",
Copy link
Owner

Choose a reason for hiding this comment

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

s/pointers/pointer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

"2022-02-07T18:00:00",
},
{
"mapping pointers to customTime",
Copy link
Owner

Choose a reason for hiding this comment

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

s/pointers/pointer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -882,17 +892,29 @@ func TestGenerator_parseExampleValue(t *testing.T) {
true,
},
{
"mapping custom structs",
reflect.TypeOf(CustomUnit{}),
"mapping customUnit",
Copy link
Owner

Choose a reason for hiding this comment

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

s/mapping/mapping to

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed.

"20.00",
"20.00 USD",
},
{
"mapping customTime",
Copy link
Owner

Choose a reason for hiding this comment

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

s/mapping/mapping to

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

README.md Outdated
@@ -231,8 +231,8 @@ You can use additional tags. Some will be interpreted by *tonic*, others will be
### Providing Examples for Structs
Copy link
Owner

Choose a reason for hiding this comment

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

Let's move it under the https://github.com/wI2L/fizz#openapi-specification section, wwhile keeping an anchor link from the "Additional tags" table.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved.

Copy link
Contributor Author

@nikicc nikicc left a comment

Choose a reason for hiding this comment

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

@wI2L pushed some more fixes. Let me know what you think.

I think I addressed everything, but the test about the example for time. I think in the current format it's easier to tests so might be in favour of keeping what we have.

README.md Outdated
@@ -231,8 +231,8 @@ You can use additional tags. Some will be interpreted by *tonic*, others will be
### Providing Examples for Structs
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved.

type OpenAPIExample interface {
ParseExample(v string) interface{}
// CustomType describes the interface of custom types that can be used in JSON responses.
type CustomType interface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to fizz/openapi/types.go.

type OpenAPIExample interface {
ParseExample(v string) interface{}
// CustomType describes the interface of custom types that can be used in JSON responses.
type CustomType interface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the meantime, I think we should keep a seperate interface, Exampler (the name sucks a bit, but we usually end interface name with "er".

Renamed to Exampler.

type OpenAPIExample interface {
ParseExample(v string) interface{}
// CustomType describes the interface of custom types that can be used in JSON responses.
type CustomType interface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ideally, this should be one single interface CustomType. Perhaps for a v1.0.

One argument for keeping these separate interfaces separate might be that it's easier to fulfil the interfaces if they are separate (e.g. if I only want to provide examples, currently I only need to implement the ParseExample method, while with one bigger interface I'd need to implement all the methods).

@@ -882,17 +892,29 @@ func TestGenerator_parseExampleValue(t *testing.T) {
true,
},
{
"mapping custom structs",
reflect.TypeOf(CustomUnit{}),
"mapping customUnit",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed.

"15",
"15 USD",
},
{
"mapping pointers to custom structs",
reflect.PtrTo(reflect.TypeOf(CustomUnit{})),
"mapping pointers to customUnit",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

"20.00",
"20.00 USD",
},
{
"mapping customTime",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

"2022-02-07T18:00:00",
},
{
"mapping pointers to customTime",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

if err != nil {
return nil, err
}
return t1.Format("2006-01-02T15:04:05"), nil
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, not sure. If we refactor, then we would need something like this:

type customTime struct {
	time.Time
}

func (c customTime) String() string {
	return c.Format("2006-01-02T15:04:05")
}

func (c customTime) ParseExample(v string) (interface{}, error) {
	t1, err := time.Parse(time.RFC3339, v)
	if err != nil {
		return nil, err
	}
	return customTime{t1}, nil
}

but then we can't test is as nicely in TestGenerator_parseExampleValue as the returned value is an object, not a string representation formatted in the the format we like. WDYT?

@nikicc nikicc requested a review from wI2L February 8, 2022 17:46
Copy link
Contributor Author

@nikicc nikicc left a comment

Choose a reason for hiding this comment

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

@wI2L pushed the updated version of a test. WDYT?

@@ -741,14 +741,20 @@ func (c customUnit) ParseExample(v string) (interface{}, error) {
return fmt.Sprintf("%s USD", v), nil
}

type customTime time.Time
type customTime struct {
Copy link
Owner

Choose a reason for hiding this comment

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

This can stay as type customTime time.Time, casting it to customTime with customTime{t1} should work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah this works. But the String method then needs an extra cast (as c.Format does not work):

func (c customTime) String() string {
	return time.Time(c).Format("2006-01-02T15:04:05")
}

Copy link
Owner

Choose a reason for hiding this comment

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

Honestly the String method is an imlem detail, it's not even necessary for the sake of the tests.
Speaking of tests, I noticed that we don't test a custom type marshaling to JSON in the final spec. This would be nice to add as well, and perhaps a mention in the README that custom types MUST implement the json.Marshaler interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ohhh, I see what you mean. We had a bit different ideas on how this should work. My initial ideas was that the ParseExample would already return the example value that can go into JSON on YAML directly (hence I also wrote some of the initial tests that way). That would be a formatted string in this case.

The way you are thinking about this is slightly different: ParseExample returns a struct and then struct gets transformed into the final JSON or YAML representation through MarshalJSON() and MarshalYAML() methods, right?

Let me refactor the tests and add a note into the README.md.

Copy link
Owner

@wI2L wI2L Feb 10, 2022

Choose a reason for hiding this comment

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

ParseExample returns a struct

Not specifically a struct, it returns a value which type is customTime (in this case), but that could be any concrete type, and yes the marshaling to JSON is implemented on that type.

That may seems counter-productive to not just return a string direclty that would be used in the final JSON of the spec, but what if the custom type has a int kind for example. In this case, we most certainly would'nt want to marshal that custom type as a JSON string but as a JSON interger, most likely. Returning a concrete type from ParseExemple and leaving the marshaling implem to the implem allows this while leaving full responsibility/flexibility to the implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I agree this is better.

Almost there with the test, going to push shortly.

@wI2L
Copy link
Owner

wI2L commented Feb 10, 2022

@nikicc One more comment, and we should be done.
Could you please squash the commits into a single one? I'll merge ASAP and release in stride.

@nikicc nikicc force-pushed the support-custom-type-examples branch from 947919a to e602795 Compare February 10, 2022 11:48
@nikicc
Copy link
Contributor Author

nikicc commented Feb 10, 2022

Could you please squash the commits into a single one?

Squashed & pushed now.

I'll merge ASAP and release in stride.

Thanks!

@nikicc nikicc force-pushed the support-custom-type-examples branch from e602795 to 479114f Compare February 10, 2022 11:50
README.md Outdated
@@ -344,6 +344,16 @@ Note that, according to the doc, the inherent version of the address is a semant

To help you write markdown descriptions in Go, a simple builder is available in the sub-package `markdown`. This is quite handy to avoid conflicts with backticks that are both used in Go for litteral multi-lines strings and code blocks in markdown.

#### Providing Examples for Structs
Copy link
Owner

Choose a reason for hiding this comment

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

"for Structs" should be replaced by "custom types". They don't necesseraly have a Struct kind.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Fixed.

README.md Outdated
@@ -223,7 +223,7 @@ You can use additional tags. Some will be interpreted by *tonic*, others will be
| `description` | Add a description of the field in the spec. |
| `deprecated` | Indicates if the field is deprecated. Accepted values are `1`, `t`, `T`, `TRUE`, `true`, `True`, `0`, `f`, `F`, `FALSE`. Invalid value are considered to be false. |
| `enum` | A coma separated list of acceptable values for the parameter. |
| `example` | An example value to be used in OpenAPI specification. |
| `example` | An example value to be used in OpenAPI specification. See [section below](#Providing-Examples-for-Structs) for the demonstration on how to provide example for struct types. |
Copy link
Owner

Choose a reason for hiding this comment

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

"for custom types" as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -1276,6 +1276,13 @@ func parseExampleValue(t reflect.Type, value string) (interface{}, error) {
return strconv.ParseFloat(value, t.Bits())
case reflect.Ptr:
return parseExampleValue(t.Elem(), value)
case reflect.Struct:
Copy link
Owner

Choose a reason for hiding this comment

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

Wondering again, since we now provide an interface, we should not restrict it to Struct kind. Instead, we should honor the ParseExemple method of every type that implement the Exampler and call it to get the example value.

For types with a Struct kind, we would default to a call to the ParseExample method, if its implemented, otherwise return an empty string to signal that there is no example.

For others kinds, we would continue with the existing switch cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wondering again, since we now provide an interface, we should not restrict it to Struct kind. Instead, we should honor the ParseExemple method of every type that implement the Exampler and call it to get the example value.

Fixed.

For types with a Struct kind, we would default to a call to the ParseExample method, if its implemented, otherwise return an empty string to signal that there is no example.

Hmm, I'm not a fan of an empty string. I think it's better if we return the error (like we currently do). If someone provided an example tag on a struct field, and the struct does not implement Exampler I think an error is more appropriate than defaulting to empty string. WDYT?

Copy link
Owner

Choose a reason for hiding this comment

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

Alright. The error doen't stop the spec generation anyway, so it's fine.

@@ -733,6 +734,27 @@ func TestSetServers(t *testing.T) {
assert.Equal(t, servers, g.API().Servers)
}

type customUnit struct {
Copy link
Owner

@wI2L wI2L Feb 10, 2022

Choose a reason for hiding this comment

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

I'd like to switch this custom type to an int or float64, to demonstrate and test that scalar go types that implement the Exampler interface can also provide their own example value.

The type could still be a struct, if we wanted to have a more complex type, such as:

type customUnit struct {
   value    float64
   currenty string
}

but that would be overkill for just tests, i'm just showcasing the possibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched to float64.

but that would be overkill for just tests, i'm just showcasing the possibility.

Yes. I think it's better to use float64 to demonstrate that this also works for non-structs.

openapi/types.go Outdated
@@ -42,6 +42,12 @@ type DataType interface {
Format() string
}

// Exampler is the interface implemented by types
Copy link
Owner

Choose a reason for hiding this comment

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

wdyt of "Exampler is the interface implemented by custom types that can parse example values." ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds nice. Changed.

Copy link
Contributor Author

@nikicc nikicc left a comment

Choose a reason for hiding this comment

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

@wI2L I addressed the latests comments but pushed in separate commits to ease the reviewing. Once we're happy with the implementation, I'll squash again. Let me know what you think.

README.md Outdated
@@ -223,7 +223,7 @@ You can use additional tags. Some will be interpreted by *tonic*, others will be
| `description` | Add a description of the field in the spec. |
| `deprecated` | Indicates if the field is deprecated. Accepted values are `1`, `t`, `T`, `TRUE`, `true`, `True`, `0`, `f`, `F`, `FALSE`. Invalid value are considered to be false. |
| `enum` | A coma separated list of acceptable values for the parameter. |
| `example` | An example value to be used in OpenAPI specification. |
| `example` | An example value to be used in OpenAPI specification. See [section below](#Providing-Examples-for-Structs) for the demonstration on how to provide example for struct types. |
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

README.md Outdated
@@ -344,6 +344,16 @@ Note that, according to the doc, the inherent version of the address is a semant

To help you write markdown descriptions in Go, a simple builder is available in the sub-package `markdown`. This is quite handy to avoid conflicts with backticks that are both used in Go for litteral multi-lines strings and code blocks in markdown.

#### Providing Examples for Structs
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Fixed.

openapi/types.go Outdated
@@ -42,6 +42,12 @@ type DataType interface {
Format() string
}

// Exampler is the interface implemented by types
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds nice. Changed.

@@ -733,6 +734,27 @@ func TestSetServers(t *testing.T) {
assert.Equal(t, servers, g.API().Servers)
}

type customUnit struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched to float64.

but that would be overkill for just tests, i'm just showcasing the possibility.

Yes. I think it's better to use float64 to demonstrate that this also works for non-structs.

@@ -1276,6 +1276,13 @@ func parseExampleValue(t reflect.Type, value string) (interface{}, error) {
return strconv.ParseFloat(value, t.Bits())
case reflect.Ptr:
return parseExampleValue(t.Elem(), value)
case reflect.Struct:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wondering again, since we now provide an interface, we should not restrict it to Struct kind. Instead, we should honor the ParseExemple method of every type that implement the Exampler and call it to get the example value.

Fixed.

For types with a Struct kind, we would default to a call to the ParseExample method, if its implemented, otherwise return an empty string to signal that there is no example.

Hmm, I'm not a fan of an empty string. I think it's better if we return the error (like we currently do). If someone provided an example tag on a struct field, and the struct does not implement Exampler I think an error is more appropriate than defaulting to empty string. WDYT?

@nikicc nikicc requested a review from wI2L February 10, 2022 15:54
@wI2L
Copy link
Owner

wI2L commented Feb 11, 2022

@nikicc LGTM, you can squash again.

@nikicc nikicc force-pushed the support-custom-type-examples branch from 10fdc74 to 0b5d99a Compare February 11, 2022 10:00
Copy link
Contributor Author

@nikicc nikicc left a comment

Choose a reason for hiding this comment

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

@nikicc LGTM, you can squash again.

@wI2L squashed & pushed. Thanks for all the detailed & prompt reviews!

@nikicc nikicc force-pushed the support-custom-type-examples branch from 0b5d99a to 815e4ae Compare February 11, 2022 10:02
@nikicc nikicc changed the title Support providing examples for custom structs Support providing examples for custom types Feb 11, 2022
Copy link
Owner

@wI2L wI2L left a comment

Choose a reason for hiding this comment

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

@nikicc Thanks for the PR.

@wI2L wI2L merged commit eb08933 into wI2L:master Feb 11, 2022
@nikicc nikicc deleted the support-custom-type-examples branch February 11, 2022 11:28
@nikicc nikicc restored the support-custom-type-examples branch February 11, 2022 11:28
@nikicc nikicc deleted the support-custom-type-examples branch February 11, 2022 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Providing Examples for Custom Types
2 participants