Skip to content

Commit

Permalink
Merge pull request #2 from zJeremiah/moved-examples
Browse files Browse the repository at this point in the history
moved the schema example into the media object as multiple examples
  • Loading branch information
zJeremiah authored Feb 2, 2023
2 parents c368922 + 8657714 commit d3a1859
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 58 deletions.
81 changes: 40 additions & 41 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,47 +218,42 @@ func (o *OpenAPI) AddRoute(path, method, tag, desc, summary string) (ur UniqueRo
m.Tags = append(m.Tags, tag)
m.OperationID = string(ur.Method) + "_" + ur.Path

// save the given route in the spec object for later reference
if o.Routes == nil {
o.Routes = make(map[UniqueRoute]Route)
}

o.Routes[ur] = Route{
Tag: tag,
Desc: desc,
}

p[ur.Method] = m
o.Paths[ur.Path] = p

return ur, nil
}

type ExampleObject struct {
Example any
Summary string
}

type BodyObject struct {
MIMEType MIMEType // the mimetype for the object
HttpStatus Code // Any HTTP status code, '200', '201', '400' the value of 'default' can be used to cover all responses not defined
Example any // the response object example used to determine the type and name of each field returned
Desc string // description of the body
Title string // object title
MIMEType MIMEType // the mimetype for the object
HttpStatus Code // Any HTTP status code, '200', '201', '400' the value of 'default' can be used to cover all responses not defined
Examples []ExampleObject // the response object examples used to determine the type and name of each field returned
Desc string // description of the body
Title string // object title
}

// NewRespBody is a helper function to create a response body object
// example is a go object to represent the body
func NewRespBody(mtype MIMEType, status Code, desc string, example any) BodyObject {
func NewRespBody(mtype MIMEType, status Code, desc string, examples []ExampleObject) BodyObject {
return BodyObject{
MIMEType: mtype,
HttpStatus: status,
Example: example,
Examples: examples,
Desc: desc,
}
}

// NewReqBody is a helper function to create a request body object
// example is a go object to represent the body
func NewReqBody(mtype MIMEType, desc string, example any) BodyObject {
func NewReqBody(mtype MIMEType, desc string, examples []ExampleObject) BodyObject {
return BodyObject{
MIMEType: mtype,
Example: example,
Examples: examples,
Desc: desc,
}
}
Expand Down Expand Up @@ -329,11 +324,12 @@ func (o *OpenAPI) AddRequest(ur UniqueRoute, bo BodyObject) error {
}

var rSchema Schema
if bo.Example != nil {
rSchema, err = buildSchema(bo.Title, bo.Desc, true, bo.Example, nil)
if len(bo.Examples) > 0 {
rSchema, err = buildSchema(bo.Title, bo.Desc, true, bo.Examples[0].Example, nil)
if err != nil {
log.Println("error building schema for endpoint", ur.Method, ur.Path)
}

}

m.RequestBody = &RequestBody{
Expand All @@ -354,50 +350,53 @@ func (o *OpenAPI) AddRequest(ur UniqueRoute, bo BodyObject) error {
// AddResponse adds response information to the api responses map which is part of the paths map
// adds an example and schema to the response body
func (o *OpenAPI) AddResponse(ur UniqueRoute, bo BodyObject) error {
p, m, err := o.PathMethod(ur.Path, ur.Method)
om, op, err := o.PathMethod(ur.Path, ur.Method)
if err != nil {
return err
}

examples := make(map[string]Example)
var rSchema Schema
if bo.Example != nil {
rSchema, err = buildSchema(bo.Title, bo.Desc, true, bo.Example, nil)
if len(bo.Examples) > 0 {
rSchema, err = buildSchema(bo.Title, bo.Desc, true, bo.Examples[0].Example, nil)
if err != nil {
return fmt.Errorf("addresp: (%s) (%s) %w", ur.Method, ur.Path, err)
}
for i, e := range bo.Examples {
name := fmt.Sprintf("%s_%s_%d", ur.Method, Method(ur.Path), i)
examples[name] = Example{
Summary: e.Summary,
Value: e.Example,
}
}
}

if m.Responses == nil {
m.Responses = make(Responses)
if op.Responses == nil {
op.Responses = make(Responses)
}

m.Responses[bo.HttpStatus] = Response{
Desc: bo.Desc,
Content: Content{
bo.MIMEType: {
Schema: rSchema,
},
r := op.Responses[bo.HttpStatus]
r.Desc = bo.Desc
r.Content = Content{
bo.MIMEType: {
Schema: rSchema,
Examples: examples,
},
}

p[ur.Method] = m
o.Paths[ur.Path] = p
op.Responses[bo.HttpStatus] = r
om[ur.Method] = op
o.Paths[ur.Path] = om

return nil
}

// BuildSchema will create a schema object based on a given example body interface
// if the example bool is true the body will be marshaled and added to the schema as an example
// BuildSchema will create a schema object based on a given example object interface
// tags is used if there is specific formatting for a given tag map[tag_name]tag_format
func buildSchema(title, desc string, example bool, body any, tags map[string]string) (s Schema, err error) {
if body == nil {
return
}

if example {
s.Example = body
}

value := reflect.ValueOf(body)
typ := reflect.TypeOf(body)
kind := typ.Kind()
Expand Down
20 changes: 10 additions & 10 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":{"customValues":[{"adate":"2023-02-01T00:00:00Z","avalue":1427200},{"bdate":"2023-01-01T00:00:00Z","bvalue":1496400}],"default":{"monthProc":[5.5,6.6,7.7,8.8],"monthTrans":[1.1,2.2,3.3,4.4]}},"properties":{"customValues":{"items":{"properties":{"adate":{"type":"string"},"avalue":{"format":"int64","type":"integer"}},"type":"object"},"type":"array"},"default":{"properties":{"monthProc":{"items":{"format":"float","type":"number"},"type":"array"},"monthTrans":{"items":{"format":"float","type":"number"},"type":"array"}},"type":"object"}},"title":"test title","type":"object"}`,
Expected: `{"description":"test description","properties":{"customValues":{"items":{"properties":{"adate":{"type":"string"},"avalue":{"format":"int64","type":"integer"}},"type":"object"},"type":"array"},"default":{"properties":{"monthProc":{"items":{"format":"float","type":"number"},"type":"array"},"monthTrans":{"items":{"format":"float","type":"number"},"type":"array"}},"type":"object"}},"title":"test title","type":"object"}`,
},
"map_test_simple": {
Input: input{
Expand All @@ -97,7 +97,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":{"key":"value"},"properties":{"key":{"type":"string"}},"title":"test title","type":"object"}`,
Expected: `{"description":"test description","properties":{"key":{"type":"string"}},"title":"test title","type":"object"}`,
},
"map_test_object": {
Input: input{
Expand All @@ -106,7 +106,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":{"keyvalue":{"field_one":123,"field_two":"string value"}},"properties":{"keyvalue":{"properties":{"field_one":{"format":"int64","type":"integer"},"field_two":{"type":"string"}},"type":"object"}},"title":"test title","type":"object"}`,
Expected: `{"description":"test description","properties":{"keyvalue":{"properties":{"field_one":{"format":"int64","type":"integer"},"field_two":{"type":"string"}},"type":"object"}},"title":"test title","type":"object"}`,
},
"nil_typed_pointer_test": {
Input: input{
Expand All @@ -119,7 +119,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":{"f1_pointer_field":{"f1_int":321,"f2_bool":true},"f2_pointer_field":null},"properties":{"f1_pointer_field":{"properties":{"f1_int":{"format":"int64","type":"integer"},"f2_bool":{"type":"boolean"}},"type":"object"},"f2_pointer_field":{}},"title":"test title","type":"object"}`,
Expected: `{"description":"test description","properties":{"f1_pointer_field":{"properties":{"f1_int":{"format":"int64","type":"integer"},"f2_bool":{"type":"boolean"}},"type":"object"},"f2_pointer_field":{}},"title":"test title","type":"object"}`,
},
"time_test": {
Input: input{
Expand All @@ -132,7 +132,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":{"openapi.time":"2023-02-02","time.time":"2023-01-11T00:00:00Z"},"properties":{"openapi.time":{"format":"2006-01-02","type":"string"},"time.time":{"format":"2006-01-02","type":"string"}},"title":"test title","type":"object"}`,
Expected: `{"description":"test description","properties":{"openapi.time":{"format":"2006-01-02","type":"string"},"time.time":{"format":"2006-01-02","type":"string"}},"title":"test title","type":"object"}`,
},
"simple_object_test": {
Input: input{
Expand All @@ -143,7 +143,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":{"field_one":"testing a","field_three":1234,"field_two":["one","two","three"]},"properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"title":"test title","type":"object"}`,
Expected: `{"description":"test description","properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"title":"test title","type":"object"}`,
},
"object_within_object": {
Input: input{
Expand All @@ -156,7 +156,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":{"b_field_one":{"field_one":"testing a","field_three":1234,"field_two":["one","two","three"]}},"properties":{"b_field_one":{"properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"title":"test title","type":"object"}`,
Expected: `{"description":"test description","properties":{"b_field_one":{"properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"title":"test title","type":"object"}`,
},
"pointer_object": {
Input: input{
Expand All @@ -169,7 +169,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":{"b_field_one":{"field_one":"testing a","field_three":1234,"field_two":["one","two","three"]}},"properties":{"b_field_one":{"properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"title":"test title","type":"object"}`,
Expected: `{"description":"test description","properties":{"b_field_one":{"properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"title":"test title","type":"object"}`,
},
"pointer_in_object": {
Input: input{
Expand All @@ -182,7 +182,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":{"e_field_one":{"field_one":"testing a","field_three":1234,"field_two":["one","two","three"]}},"properties":{"e_field_one":{"properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"title":"test title","type":"object"}`,
Expected: `{"description":"test description","properties":{"e_field_one":{"properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"title":"test title","type":"object"}`,
},
"array_of_array_objects": {
Input: input{
Expand All @@ -202,7 +202,7 @@ func TestBuildSchema(t *testing.T) {
},
print: false,
},
Expected: `{"description":"test description","example":[{"c_field_one":[{"field_one":"testing slice 1","field_three":987,"field_two":["nine","eight","seven"]},{"field_one":"testing slice 2","field_three":321,"field_two":["three","two","one"]}]}],"items":{"properties":{"c_field_one":{"items":{"properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"type":"object"},"type":"array"}},"type":"object"},"title":"test title","type":"array"}`,
Expected: `{"description":"test description","items":{"properties":{"c_field_one":{"items":{"properties":{"field_one":{"type":"string"},"field_three":{"format":"int64","type":"integer"},"field_two":{"items":{"type":"string"},"type":"array"}},"type":"object"},"type":"array"}},"type":"object"},"title":"test title","type":"array"}`,
},
}

Expand Down
30 changes: 23 additions & 7 deletions openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ type OpenAPI struct {
Info Info `json:"info"` // REQUIRED. Provides metadata about the API. The metadata MAY be used by tooling as required.
//Components Components `json:"components,omitempty"` // reuseable components not used here
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` //Additional external documentation.

// non OpenAPI external reference for simplified routes
Routes map[UniqueRoute]Route `json:"-"`
}

// Operation describes a single API operation on a path.
Expand Down Expand Up @@ -47,14 +44,33 @@ type Response struct {
}

type Media struct {
Schema Schema `json:"schema"`
Schema Schema `json:"schema,omitempty"` // The schema defining the content of the request, response, or parameter.
Example any `json:"example,omitempty"` // Example of the media type. The example object SHOULD be in the correct format as specified by the media type. The example field is mutually exclusive of the examples field. Furthermore, if referencing a schema which contains an example, the example value SHALL override the example provided by the schema.
Examples map[string]Example `json:"examples,omitempty"` // Examples of the media type. Each example object SHOULD match the media type and specified schema if present. The examples field is mutually exclusive of the example field. Furthermore, if referencing a schema which contains an example, the examples value SHALL override the example provided by the schema.
Encoding map[string]Encoding `json:"encoding,omitempty"` // A map between a property name and its encoding information. The key, being the property name, MUST exist in the schema as a property.
}

type Encoding struct {
ContentType string `json:"contentType,omitempty"` // The Content-Type for encoding a specific property.
// headers map[string]headerObject : not implemented needed if media is multipart
Style string `json:"style"` // Describes how a specific property value will be serialized depending on its type.
// explode bool not implemented needed if media is application/x-www-form-urlencoded
// allowReserved bool not implemented needed if media is application/x-www-form-urlencoded
}

// Example object MAY be extended with Specification Extensions.
type Example struct {
Summary string `json:"summary"` // Short description for the example.
Desc string `json:"descvription"` // Long description for the example. CommonMark syntax MAY be used for rich text representation.
ExternalValue string `json:"externalValue"` // A URL that points to the literal example. This provides the capability to reference examples that cannot easily be included in JSON or YAML documents. The value field and externalValue field are mutually exclusive.
Value any `json:"value"` // Embedded literal example. The value field and externalValue field are mutually exclusive. To represent examples of media types that cannot naturally represented in JSON or YAML, use a string value to contain the example, escaping where necessary.
}

// RequestBody describes a single request body.
type RequestBody struct {
Desc string `json:"description,omitempty"`
Content Content `json:"content,omitempty"`
Required *bool `json:"required,omitempty"`
Desc string `json:"description,omitempty"` // A brief description of the request body. This could contain examples of use. CommonMark syntax MAY be used for rich text representation.
Content Content `json:"content,omitempty"` // REQUIRED. The content of the request body. The key is a media type or media type range and the value describes it. For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
Required *bool `json:"required,omitempty"` // Determines if the request body is required in the request. Defaults to false.
}

// Schema Object allows the definition of input and output data types
Expand Down

0 comments on commit d3a1859

Please sign in to comment.