Repository: ogen-go/ogen
Pull request #72 (2021-12-10 14:03:11)

fix(gen): wrap body with Optional if body is not required


Patch:
diff --git a/internal/gen/_template/req_dec.tmpl b/internal/gen/_template/req_dec.tmpl
index 70417042..baeb6b46 100644
--- a/internal/gen/_template/req_dec.tmpl
+++ b/internal/gen/_template/req_dec.tmpl
@@ -15,12 +15,21 @@ func decode{{ $op.Name }}Request(r *http.Request, span trace.Span) (req {{ $op.R
 		return {{ $op.Request.Type.Go }}{Data: r.Body}, nil
 		{{- /* Decode json */ -}}
 		{{- else if $contentType.JSON }}
+		{{- if $t.GenericVariant.Optional }}
+        if r.ContentLength == 0 {
+			return req, nil
+		}
+        {{- end }}
 		var request {{ $t.Go }}
 		buf := getBuf()
 		defer putBuf(buf)
-		if _, err := io.Copy(buf, r.Body); err != nil {
+		written, err := io.Copy(buf, r.Body)
+		if err != nil {
 			return req, err
 		}
+		if written == 0 {
+			return req, nil
+		}
 		d := jx.GetDecoder()
 		defer jx.PutDecoder(d)
 		d.ResetBytes(buf.Bytes())
diff --git a/internal/gen/gen_request_body.go b/internal/gen/gen_request_body.go
index 28628555..566a8fcb 100644
--- a/internal/gen/gen_request_body.go
+++ b/internal/gen/gen_request_body.go
@@ -48,6 +48,12 @@ func (g *Generator) generateRequest(opName string, body *oas.RequestBody) (*ir.R
 		if err != nil {
 			return nil, errors.Wrapf(err, "contents: %s", contentType)
 		}
+		if !body.Required {
+			t = ir.Generic(genericPostfix(t.Go()), t, ir.GenericVariant{
+				Optional: true,
+			})
+			g.saveType(t)
+		}
 
 		types[ir.ContentType(contentType)] = t
 	}
@@ -57,7 +63,6 @@ func (g *Generator) generateRequest(opName string, body *oas.RequestBody) (*ir.R
 			return &ir.Request{
 				Type:     t,
 				Contents: types,
-				Required: body.Required,
 				Spec:     body,
 			}, nil
 		}
@@ -85,7 +90,6 @@ func (g *Generator) generateRequest(opName string, body *oas.RequestBody) (*ir.R
 	return &ir.Request{
 		Type:     iface,
 		Contents: types,
-		Required: body.Required,
 		Spec:     body,
 	}, nil
 }
diff --git a/internal/gen/reduce.go b/internal/gen/reduce.go
index 3a2c3f1d..f6513fd4 100644
--- a/internal/gen/reduce.go
+++ b/internal/gen/reduce.go
@@ -281,7 +281,6 @@ func cloneRequest(r *ir.Request) *ir.Request {
 	return &ir.Request{
 		Type:     r.Type,
 		Contents: contents,
-		Required: r.Required,
 		Spec:     r.Spec,
 	}
 }
diff --git a/internal/ir/operation.go b/internal/ir/operation.go
index f50f400e..4ac39328 100644
--- a/internal/ir/operation.go
+++ b/internal/ir/operation.go
@@ -38,7 +38,6 @@ type Parameter struct {
 type Request struct {
 	Type     *Type
 	Contents map[ContentType]*Type
-	Required bool
 	Spec     *oas.RequestBody
 }
 
diff --git a/internal/sample_api/oas_client_gen.go b/internal/sample_api/oas_client_gen.go
index de3f4482..1e154653 100644
--- a/internal/sample_api/oas_client_gen.go
+++ b/internal/sample_api/oas_client_gen.go
@@ -205,12 +205,20 @@ func (c *Client) FoobarGet(ctx context.Context, params FoobarGetParams) (res Foo
 // FoobarPost invokes foobarPost operation.
 //
 // POST /foobar
-func (c *Client) FoobarPost(ctx context.Context, request Pet) (res FoobarPostRes, err error) {
+func (c *Client) FoobarPost(ctx context.Context, request OptPet) (res FoobarPostRes, err error) {
 	if err := func() error {
-		if err := request.Validate(); err != nil {
-			return err
+		if request.Set {
+			if err := func() error {
+				if err := request.Value.Validate(); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return err
+			}
 		}
 		return nil
+		return nil
 	}(); err != nil {
 		return res, errors.Wrap(err, "validate")
 	}
@@ -360,12 +368,20 @@ func (c *Client) GetHeader(ctx context.Context, params GetHeaderParams) (res Has
 // PetCreate invokes petCreate operation.
 //
 // POST /pet
-func (c *Client) PetCreate(ctx context.Context, request Pet) (res Pet, err error) {
+func (c *Client) PetCreate(ctx context.Context, request OptPet) (res Pet, err error) {
 	if err := func() error {
-		if err := request.Validate(); err != nil {
-			return err
+		if request.Set {
+			if err := func() error {
+				if err := request.Value.Validate(); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return err
+			}
 		}
 		return nil
+		return nil
 	}(); err != nil {
 		return res, errors.Wrap(err, "validate")
 	}
@@ -754,12 +770,20 @@ func (c *Client) PetNameByID(ctx context.Context, params PetNameByIDParams) (res
 // PetUpdateNameAliasPost invokes  operation.
 //
 // POST /pet/updateNameAlias
-func (c *Client) PetUpdateNameAliasPost(ctx context.Context, request PetName) (res PetUpdateNameAliasPostDefStatusCode, err error) {
+func (c *Client) PetUpdateNameAliasPost(ctx context.Context, request OptPetName) (res PetUpdateNameAliasPostDefStatusCode, err error) {
 	if err := func() error {
-		if err := request.Validate(); err != nil {
-			return err
+		if request.Set {
+			if err := func() error {
+				if err := request.Value.Validate(); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return err
+			}
 		}
 		return nil
+		return nil
 	}(); err != nil {
 		return res, errors.Wrap(err, "validate")
 	}
@@ -815,20 +839,28 @@ func (c *Client) PetUpdateNameAliasPost(ctx context.Context, request PetName) (r
 // PetUpdateNamePost invokes  operation.
 //
 // POST /pet/updateName
-func (c *Client) PetUpdateNamePost(ctx context.Context, request string) (res PetUpdateNamePostDefStatusCode, err error) {
+func (c *Client) PetUpdateNamePost(ctx context.Context, request OptString) (res PetUpdateNamePostDefStatusCode, err error) {
 	if err := func() error {
-		if err := (validate.String{
-			MinLength:    6,
-			MinLengthSet: true,
-			MaxLength:    0,
-			MaxLengthSet: false,
-			Email:        false,
-			Hostname:     false,
-			Regex:        nil,
-		}).Validate(string(request)); err != nil {
-			return errors.Wrap(err, "string")
+		if request.Set {
+			if err := func() error {
+				if err := (validate.String{
+					MinLength:    6,
+					MinLengthSet: true,
+					MaxLength:    0,
+					MaxLengthSet: false,
+					Email:        false,
+					Hostname:     false,
+					Regex:        nil,
+				}).Validate(string(request.Value)); err != nil {
+					return errors.Wrap(err, "string")
+				}
+				return nil
+			}(); err != nil {
+				return err
+			}
 		}
 		return nil
+		return nil
 	}(); err != nil {
 		return res, errors.Wrap(err, "validate")
 	}
diff --git a/internal/sample_api/oas_json_gen.go b/internal/sample_api/oas_json_gen.go
index a6bccd20..f75dca44 100644
--- a/internal/sample_api/oas_json_gen.go
+++ b/internal/sample_api/oas_json_gen.go
@@ -619,6 +619,46 @@ func (o *OptNilString) Decode(d *jx.Decoder) error {
 	}
 }
 
+// Encode encodes Pet as json.
+func (o OptPet) Encode(e *jx.Encoder) {
+	o.Value.Encode(e)
+}
+
+// Decode decodes Pet from json.
+func (o *OptPet) Decode(d *jx.Decoder) error {
+	if o == nil {
+		return errors.New(`invalid: unable to decode OptPet to nil`)
+	}
+	switch d.Next() {
+	case jx.Object:
+		o.Set = true
+		if err := o.Value.Decode(d); err != nil {
+			return err
+		}
+		return nil
+	default:
+		return errors.Errorf(`unexpected type %q while reading OptPet`, d.Next())
+	}
+}
+
+// Encode encodes PetName as json.
+func (o OptPetName) Encode(e *jx.Encoder) {
+}
+
+// Decode decodes PetName from json.
+func (o *OptPetName) Decode(d *jx.Decoder) error {
+	if o == nil {
+		return errors.New(`invalid: unable to decode OptPetName to nil`)
+	}
+	switch d.Next() {
+	case jx.String:
+		o.Set = true
+		return nil
+	default:
+		return errors.Errorf(`unexpected type %q while reading OptPetName`, d.Next())
+	}
+}
+
 // Encode encodes PetType as json.
 func (o OptPetType) Encode(e *jx.Encoder) {
 	e.Str(string(o.Value))
@@ -643,6 +683,30 @@ func (o *OptPetType) Decode(d *jx.Decoder) error {
 	}
 }
 
+// Encode encodes string as json.
+func (o OptString) Encode(e *jx.Encoder) {
+	e.Str(string(o.Value))
+}
+
+// Decode decodes string from json.
+func (o *OptString) Decode(d *jx.Decoder) error {
+	if o == nil {
+		return errors.New(`invalid: unable to decode OptString to nil`)
+	}
+	switch d.Next() {
+	case jx.String:
+		o.Set = true
+		v, err := d.Str()
+		if err != nil {
+			return err
+		}
+		o.Value = string(v)
+		return nil
+	default:
+		return errors.Errorf(`unexpected type %q while reading OptString`, d.Next())
+	}
+}
+
 // Encode encodes time.Time as json.
 func (o OptTime) Encode(e *jx.Encoder, format func(*jx.Encoder, time.Time)) {
 	format(e, o.Value)
diff --git a/internal/sample_api/oas_req_dec_gen.go b/internal/sample_api/oas_req_dec_gen.go
index f60cba27..1b1c3eb3 100644
--- a/internal/sample_api/oas_req_dec_gen.go
+++ b/internal/sample_api/oas_req_dec_gen.go
@@ -62,19 +62,27 @@ var (
 	_ = sync.Pool{}
 )
 
-func decodeFoobarPostRequest(r *http.Request, span trace.Span) (req Pet, err error) {
+func decodeFoobarPostRequest(r *http.Request, span trace.Span) (req OptPet, err error) {
 	switch r.Header.Get("Content-Type") {
 	case "application/json":
-		var request Pet
+		if r.ContentLength == 0 {
+			return req, nil
+		}
+		var request OptPet
 		buf := getBuf()
 		defer putBuf(buf)
-		if _, err := io.Copy(buf, r.Body); err != nil {
+		written, err := io.Copy(buf, r.Body)
+		if err != nil {
 			return req, err
 		}
+		if written == 0 {
+			return req, nil
+		}
 		d := jx.GetDecoder()
 		defer jx.PutDecoder(d)
 		d.ResetBytes(buf.Bytes())
 		if err := func() error {
+			request.Reset()
 			if err := request.Decode(d); err != nil {
 				return err
 			}
@@ -83,10 +91,18 @@ func decodeFoobarPostRequest(r *http.Request, span trace.Span) (req Pet, err err
 			return req, err
 		}
 		if err := func() error {
-			if err := request.Validate(); err != nil {
-				return err
+			if request.Set {
+				if err := func() error {
+					if err := request.Value.Validate(); err != nil {
+						return err
+					}
+					return nil
+				}(); err != nil {
+					return err
+				}
 			}
 			return nil
+			return nil
 		}(); err != nil {
 			return req, errors.Wrap(err, "validate")
 		}
@@ -96,19 +112,27 @@ func decodeFoobarPostRequest(r *http.Request, span trace.Span) (req Pet, err err
 	}
 }
 
-func decodePetCreateRequest(r *http.Request, span trace.Span) (req Pet, err error) {
+func decodePetCreateRequest(r *http.Request, span trace.Span) (req OptPet, err error) {
 	switch r.Header.Get("Content-Type") {
 	case "application/json":
-		var request Pet
+		if r.ContentLength == 0 {
+			return req, nil
+		}
+		var request OptPet
 		buf := getBuf()
 		defer putBuf(buf)
-		if _, err := io.Copy(buf, r.Body); err != nil {
+		written, err := io.Copy(buf, r.Body)
+		if err != nil {
 			return req, err
 		}
+		if written == 0 {
+			return req, nil
+		}
 		d := jx.GetDecoder()
 		defer jx.PutDecoder(d)
 		d.ResetBytes(buf.Bytes())
 		if err := func() error {
+			request.Reset()
 			if err := request.Decode(d); err != nil {
 				return err
 			}
@@ -117,10 +141,18 @@ func decodePetCreateRequest(r *http.Request, span trace.Span) (req Pet, err erro
 			return req, err
 		}
 		if err := func() error {
-			if err := request.Validate(); err != nil {
-				return err
+			if request.Set {
+				if err := func() error {
+					if err := request.Value.Validate(); err != nil {
+						return err
+					}
+					return nil
+				}(); err != nil {
+					return err
+				}
 			}
 			return nil
+			return nil
 		}(); err != nil {
 			return req, errors.Wrap(err, "validate")
 		}
@@ -130,37 +162,47 @@ func decodePetCreateRequest(r *http.Request, span trace.Span) (req Pet, err erro
 	}
 }
 
-func decodePetUpdateNameAliasPostRequest(r *http.Request, span trace.Span) (req PetName, err error) {
+func decodePetUpdateNameAliasPostRequest(r *http.Request, span trace.Span) (req OptPetName, err error) {
 	switch r.Header.Get("Content-Type") {
 	case "application/json":
-		var request PetName
+		if r.ContentLength == 0 {
+			return req, nil
+		}
+		var request OptPetName
 		buf := getBuf()
 		defer putBuf(buf)
-		if _, err := io.Copy(buf, r.Body); err != nil {
+		written, err := io.Copy(buf, r.Body)
+		if err != nil {
 			return req, err
 		}
+		if written == 0 {
+			return req, nil
+		}
 		d := jx.GetDecoder()
 		defer jx.PutDecoder(d)
 		d.ResetBytes(buf.Bytes())
 		if err := func() error {
-			{
-				var unwrapped string
-				v, err := d.Str()
-				unwrapped = string(v)
-				if err != nil {
-					return err
-				}
-				request = PetName(unwrapped)
+			request.Reset()
+			if err := request.Decode(d); err != nil {
+				return err
 			}
 			return nil
 		}(); err != nil {
 			return req, err
 		}
 		if err := func() error {
-			if err := request.Validate(); err != nil {
-				return err
+			if request.Set {
+				if err := func() error {
+					if err := request.Value.Validate(); err != nil {
+						return err
+					}
+					return nil
+				}(); err != nil {
+					return err
+				}
 			}
 			return nil
+			return nil
 		}(); err != nil {
 			return req, errors.Wrap(err, "validate")
 		}
@@ -170,22 +212,28 @@ func decodePetUpdateNameAliasPostRequest(r *http.Request, span trace.Span) (req
 	}
 }
 
-func decodePetUpdateNamePostRequest(r *http.Request, span trace.Span) (req string, err error) {
+func decodePetUpdateNamePostRequest(r *http.Request, span trace.Span) (req OptString, err error) {
 	switch r.Header.Get("Content-Type") {
 	case "application/json":
-		var request string
+		if r.ContentLength == 0 {
+			return req, nil
+		}
+		var request OptString
 		buf := getBuf()
 		defer putBuf(buf)
-		if _, err := io.Copy(buf, r.Body); err != nil {
+		written, err := io.Copy(buf, r.Body)
+		if err != nil {
 			return req, err
 		}
+		if written == 0 {
+			return req, nil
+		}
 		d := jx.GetDecoder()
 		defer jx.PutDecoder(d)
 		d.ResetBytes(buf.Bytes())
 		if err := func() error {
-			v, err := d.Str()
-			request = string(v)
-			if err != nil {
+			request.Reset()
+			if err := request.Decode(d); err != nil {
 				return err
 			}
 			return nil
@@ -193,18 +241,26 @@ func decodePetUpdateNamePostRequest(r *http.Request, span trace.Span) (req strin
 			return req, err
 		}
 		if err := func() error {
-			if err := (validate.String{
-				MinLength:    6,
-				MinLengthSet: true,
-				MaxLength:    0,
-				MaxLengthSet: false,
-				Email:        false,
-				Hostname:     false,
-				Regex:        nil,
-			}).Validate(string(request)); err != nil {
-				return errors.Wrap(err, "string")
+			if request.Set {
+				if err := func() error {
+					if err := (validate.String{
+						MinLength:    6,
+						MinLengthSet: true,
+						MaxLength:    0,
+						MaxLengthSet: false,
+						Email:        false,
+						Hostname:     false,
+						Regex:        nil,
+					}).Validate(string(request.Value)); err != nil {
+						return errors.Wrap(err, "string")
+					}
+					return nil
+				}(); err != nil {
+					return err
+				}
 			}
 			return nil
+			return nil
 		}(); err != nil {
 			return req, errors.Wrap(err, "validate")
 		}
diff --git a/internal/sample_api/oas_req_enc_gen.go b/internal/sample_api/oas_req_enc_gen.go
index 91d8a158..d9f5756f 100644
--- a/internal/sample_api/oas_req_enc_gen.go
+++ b/internal/sample_api/oas_req_enc_gen.go
@@ -62,12 +62,13 @@ var (
 	_ = sync.Pool{}
 )
 
-func encodeFoobarPostRequestJSON(req Pet, span trace.Span) (data *bytes.Buffer, err error) {
+func encodeFoobarPostRequestJSON(req OptPet, span trace.Span) (data *bytes.Buffer, err error) {
 	buf := getBuf()
 	e := jx.GetEncoder()
 	defer jx.PutEncoder(e)
-
-	req.Encode(e)
+	if req.Set {
+		req.Encode(e)
+	}
 	if _, err := e.WriteTo(buf); err != nil {
 		putBuf(buf)
 		return nil, err
@@ -76,12 +77,13 @@ func encodeFoobarPostRequestJSON(req Pet, span trace.Span) (data *bytes.Buffer,
 	return buf, nil
 }
 
-func encodePetCreateRequestJSON(req Pet, span trace.Span) (data *bytes.Buffer, err error) {
+func encodePetCreateRequestJSON(req OptPet, span trace.Span) (data *bytes.Buffer, err error) {
 	buf := getBuf()
 	e := jx.GetEncoder()
 	defer jx.PutEncoder(e)
-
-	req.Encode(e)
+	if req.Set {
+		req.Encode(e)
+	}
 	if _, err := e.WriteTo(buf); err != nil {
 		putBuf(buf)
 		return nil, err
@@ -90,12 +92,13 @@ func encodePetCreateRequestJSON(req Pet, span trace.Span) (data *bytes.Buffer, e
 	return buf, nil
 }
 
-func encodePetUpdateNameAliasPostRequestJSON(req PetName, span trace.Span) (data *bytes.Buffer, err error) {
+func encodePetUpdateNameAliasPostRequestJSON(req OptPetName, span trace.Span) (data *bytes.Buffer, err error) {
 	buf := getBuf()
 	e := jx.GetEncoder()
 	defer jx.PutEncoder(e)
-
-	req.Encode(e)
+	if req.Set {
+		req.Encode(e)
+	}
 	if _, err := e.WriteTo(buf); err != nil {
 		putBuf(buf)
 		return nil, err
@@ -104,12 +107,13 @@ func encodePetUpdateNameAliasPostRequestJSON(req PetName, span trace.Span) (data
 	return buf, nil
 }
 
-func encodePetUpdateNamePostRequestJSON(req string, span trace.Span) (data *bytes.Buffer, err error) {
+func encodePetUpdateNamePostRequestJSON(req OptString, span trace.Span) (data *bytes.Buffer, err error) {
 	buf := getBuf()
 	e := jx.GetEncoder()
 	defer jx.PutEncoder(e)
-
-	e.Str(req)
+	if req.Set {
+		req.Encode(e)
+	}
 	if _, err := e.WriteTo(buf); err != nil {
 		putBuf(buf)
 		return nil, err
diff --git a/internal/sample_api/oas_schemas_gen.go b/internal/sample_api/oas_schemas_gen.go
index 64781b81..34d455e8 100644
--- a/internal/sample_api/oas_schemas_gen.go
+++ b/internal/sample_api/oas_schemas_gen.go
@@ -483,6 +483,82 @@ func (o OptNilString) Get() (v string, ok bool) {
 	return o.Value, true
 }
 
+// NewOptPet returns new OptPet with value set to v.
+func NewOptPet(v Pet) OptPet {
+	return OptPet{
+		Value: v,
+		Set:   true,
+	}
+}
+
+// OptPet is optional Pet.
+type OptPet struct {
+	Value Pet
+	Set   bool
+}
+
+// IsSet returns true if OptPet was set.
+func (o OptPet) IsSet() bool { return o.Set }
+
+// Reset unsets value.
+func (o *OptPet) Reset() {
+	var v Pet
+	o.Value = v
+	o.Set = false
+}
+
+// SetTo sets value to v.
+func (o *OptPet) SetTo(v Pet) {
+	o.Set = true
+	o.Value = v
+}
+
+// Get returns value and boolean that denotes whether value was set.
+func (o OptPet) Get() (v Pet, ok bool) {
+	if !o.Set {
+		return v, false
+	}
+	return o.Value, true
+}
+
+// NewOptPetName returns new OptPetName with value set to v.
+func NewOptPetName(v PetName) OptPetName {
+	return OptPetName{
+		Value: v,
+		Set:   true,
+	}
+}
+
+// OptPetName is optional PetName.
+type OptPetName struct {
+	Value PetName
+	Set   bool
+}
+
+// IsSet returns true if OptPetName was set.
+func (o OptPetName) IsSet() bool { return o.Set }
+
+// Reset unsets value.
+func (o *OptPetName) Reset() {
+	var v PetName
+	o.Value = v
+	o.Set = false
+}
+
+// SetTo sets value to v.
+func (o *OptPetName) SetTo(v PetName) {
+	o.Set = true
+	o.Value = v
+}
+
+// Get returns value and boolean that denotes whether value was set.
+func (o OptPetName) Get() (v PetName, ok bool) {
+	if !o.Set {
+		return v, false
+	}
+	return o.Value, true
+}
+
 // NewOptPetType returns new OptPetType with value set to v.
 func NewOptPetType(v PetType) OptPetType {
 	return OptPetType{
@@ -521,6 +597,44 @@ func (o OptPetType) Get() (v PetType, ok bool) {
 	return o.Value, true
 }
 
+// NewOptString returns new OptString with value set to v.
+func NewOptString(v string) OptString {
+	return OptString{
+		Value: v,
+		Set:   true,
+	}
+}
+
+// OptString is optional string.
+type OptString struct {
+	Value string
+	Set   bool
+}
+
+// IsSet returns true if OptString was set.
+func (o OptString) IsSet() bool { return o.Set }
+
+// Reset unsets value.
+func (o *OptString) Reset() {
+	var v string
+	o.Value = v
+	o.Set = false
+}
+
+// SetTo sets value to v.
+func (o *OptString) SetTo(v string) {
+	o.Set = true
+	o.Value = v
+}
+
+// Get returns value and boolean that denotes whether value was set.
+func (o OptString) Get() (v string, ok bool) {
+	if !o.Set {
+		return v, false
+	}
+	return o.Value, true
+}
+
 // NewOptTime returns new OptTime with value set to v.
 func NewOptTime(v time.Time) OptTime {
 	return OptTime{
diff --git a/internal/sample_api/oas_server_gen.go b/internal/sample_api/oas_server_gen.go
index 9372d622..42d8b93c 100644
--- a/internal/sample_api/oas_server_gen.go
+++ b/internal/sample_api/oas_server_gen.go
@@ -75,7 +75,7 @@ type Handler interface {
 	// FoobarPost implements foobarPost operation.
 	//
 	// POST /foobar
-	FoobarPost(ctx context.Context, req Pet) (FoobarPostRes, error)
+	FoobarPost(ctx context.Context, req OptPet) (FoobarPostRes, error)
 	// FoobarPut implements  operation.
 	//
 	// PUT /foobar
@@ -87,7 +87,7 @@ type Handler interface {
 	// PetCreate implements petCreate operation.
 	//
 	// POST /pet
-	PetCreate(ctx context.Context, req Pet) (Pet, error)
+	PetCreate(ctx context.Context, req OptPet) (Pet, error)
 	// PetFriendsNamesByID implements petFriendsNamesByID operation.
 	//
 	// GET /pet/friendNames/{id}
@@ -111,11 +111,11 @@ type Handler interface {
 	// PetUpdateNameAliasPost implements  operation.
 	//
 	// POST /pet/updateNameAlias
-	PetUpdateNameAliasPost(ctx context.Context, req PetName) (PetUpdateNameAliasPostDefStatusCode, error)
+	PetUpdateNameAliasPost(ctx context.Context, req OptPetName) (PetUpdateNameAliasPostDefStatusCode, error)
 	// PetUpdateNamePost implements  operation.
 	//
 	// POST /pet/updateName
-	PetUpdateNamePost(ctx context.Context, req string) (PetUpdateNamePostDefStatusCode, error)
+	PetUpdateNamePost(ctx context.Context, req OptString) (PetUpdateNamePostDefStatusCode, error)
 	// PetUploadAvatarByID implements petUploadAvatarByID operation.
 	//
 	// POST /pet/avatar
diff --git a/internal/sample_err/oas_client_gen.go b/internal/sample_err/oas_client_gen.go
index 737aa726..ffe27990 100644
--- a/internal/sample_err/oas_client_gen.go
+++ b/internal/sample_err/oas_client_gen.go
@@ -96,7 +96,7 @@ func NewClient(serverURL string, opts ...Option) (*Client, error) {
 // DataCreate invokes dataCreate operation.
 //
 // POST /data
-func (c *Client) DataCreate(ctx context.Context, request Data) (res Data, err error) {
+func (c *Client) DataCreate(ctx context.Context, request OptData) (res Data, err error) {
 	startTime := time.Now()
 	ctx, span := c.cfg.Tracer.Start(ctx, `DataCreate`,
 		trace.WithAttributes(otelogen.OperationID(`dataCreate`)),
diff --git a/internal/sample_err/oas_json_gen.go b/internal/sample_err/oas_json_gen.go
index 7d8e90ca..05aa9581 100644
--- a/internal/sample_err/oas_json_gen.go
+++ b/internal/sample_err/oas_json_gen.go
@@ -148,3 +148,25 @@ func (s *ErrorStatusCode) Decode(d *jx.Decoder) error {
 		return nil
 	})
 }
+
+// Encode encodes Data as json.
+func (o OptData) Encode(e *jx.Encoder) {
+	o.Value.Encode(e)
+}
+
+// Decode decodes Data from json.
+func (o *OptData) Decode(d *jx.Decoder) error {
+	if o == nil {
+		return errors.New(`invalid: unable to decode OptData to nil`)
+	}
+	switch d.Next() {
+	case jx.Object:
+		o.Set = true
+		if err := o.Value.Decode(d); err != nil {
+			return err
+		}
+		return nil
+	default:
+		return errors.Errorf(`unexpected type %q while reading OptData`, d.Next())
+	}
+}
diff --git a/internal/sample_err/oas_req_dec_gen.go b/internal/sample_err/oas_req_dec_gen.go
index 5e7caecd..c7c00fda 100644
--- a/internal/sample_err/oas_req_dec_gen.go
+++ b/internal/sample_err/oas_req_dec_gen.go
@@ -62,19 +62,27 @@ var (
 	_ = sync.Pool{}
 )
 
-func decodeDataCreateRequest(r *http.Request, span trace.Span) (req Data, err error) {
+func decodeDataCreateRequest(r *http.Request, span trace.Span) (req OptData, err error) {
 	switch r.Header.Get("Content-Type") {
 	case "application/json":
-		var request Data
+		if r.ContentLength == 0 {
+			return req, nil
+		}
+		var request OptData
 		buf := getBuf()
 		defer putBuf(buf)
-		if _, err := io.Copy(buf, r.Body); err != nil {
+		written, err := io.Copy(buf, r.Body)
+		if err != nil {
 			return req, err
 		}
+		if written == 0 {
+			return req, nil
+		}
 		d := jx.GetDecoder()
 		defer jx.PutDecoder(d)
 		d.ResetBytes(buf.Bytes())
 		if err := func() error {
+			request.Reset()
 			if err := request.Decode(d); err != nil {
 				return err
 			}
diff --git a/internal/sample_err/oas_req_enc_gen.go b/internal/sample_err/oas_req_enc_gen.go
index 832c3b6d..794440d0 100644
--- a/internal/sample_err/oas_req_enc_gen.go
+++ b/internal/sample_err/oas_req_enc_gen.go
@@ -62,12 +62,13 @@ var (
 	_ = sync.Pool{}
 )
 
-func encodeDataCreateRequestJSON(req Data, span trace.Span) (data *bytes.Buffer, err error) {
+func encodeDataCreateRequestJSON(req OptData, span trace.Span) (data *bytes.Buffer, err error) {
 	buf := getBuf()
 	e := jx.GetEncoder()
 	defer jx.PutEncoder(e)
-
-	req.Encode(e)
+	if req.Set {
+		req.Encode(e)
+	}
 	if _, err := e.WriteTo(buf); err != nil {
 		putBuf(buf)
 		return nil, err
diff --git a/internal/sample_err/oas_schemas_gen.go b/internal/sample_err/oas_schemas_gen.go
index 21cea990..066aeb66 100644
--- a/internal/sample_err/oas_schemas_gen.go
+++ b/internal/sample_err/oas_schemas_gen.go
@@ -82,3 +82,41 @@ type ErrorStatusCode struct {
 	StatusCode int
 	Response   Error
 }
+
+// NewOptData returns new OptData with value set to v.
+func NewOptData(v Data) OptData {
+	return OptData{
+		Value: v,
+		Set:   true,
+	}
+}
+
+// OptData is optional Data.
+type OptData struct {
+	Value Data
+	Set   bool
+}
+
+// IsSet returns true if OptData was set.
+func (o OptData) IsSet() bool { return o.Set }
+
+// Reset unsets value.
+func (o *OptData) Reset() {
+	var v Data
+	o.Value = v
+	o.Set = false
+}
+
+// SetTo sets value to v.
+func (o *OptData) SetTo(v Data) {
+	o.Set = true
+	o.Value = v
+}
+
+// Get returns value and boolean that denotes whether value was set.
+func (o OptData) Get() (v Data, ok bool) {
+	if !o.Set {
+		return v, false
+	}
+	return o.Value, true
+}
diff --git a/internal/sample_err/oas_server_gen.go b/internal/sample_err/oas_server_gen.go
index c0ae9a22..44694801 100644
--- a/internal/sample_err/oas_server_gen.go
+++ b/internal/sample_err/oas_server_gen.go
@@ -67,7 +67,7 @@ type Handler interface {
 	// DataCreate implements dataCreate operation.
 	//
 	// POST /data
-	DataCreate(ctx context.Context, req Data) (Data, error)
+	DataCreate(ctx context.Context, req OptData) (Data, error)
 	// DataGet implements dataGet operation.
 	//
 	// GET /data


Test patch:
diff --git a/e2e_err_test.go b/e2e_err_test.go
index 25d2508e..e1d22ada 100644
--- a/e2e_err_test.go
+++ b/e2e_err_test.go
@@ -13,7 +13,7 @@ import (
 type sampleErrServer struct {
 }
 
-func (s sampleErrServer) DataCreate(ctx context.Context, req api.Data) (api.Data, error) {
+func (s sampleErrServer) DataCreate(ctx context.Context, req api.OptData) (api.Data, error) {
 	panic("implement me")
 }
 
diff --git a/e2e_test.go b/e2e_test.go
index 9f9d6c63..5e741bea 100644
--- a/e2e_test.go
+++ b/e2e_test.go
@@ -75,11 +75,11 @@ func (s sampleAPIServer) GetHeader(ctx context.Context, params api.GetHeaderPara
 	}, nil
 }
 
-func (s sampleAPIServer) PetUpdateNameAliasPost(ctx context.Context, req api.PetName) (api.PetUpdateNameAliasPostDefStatusCode, error) {
+func (s sampleAPIServer) PetUpdateNameAliasPost(ctx context.Context, req api.OptPetName) (api.PetUpdateNameAliasPostDefStatusCode, error) {
 	panic("implement me")
 }
 
-func (s sampleAPIServer) PetUpdateNamePost(ctx context.Context, req string) (api.PetUpdateNamePostDefStatusCode, error) {
+func (s sampleAPIServer) PetUpdateNamePost(ctx context.Context, req api.OptString) (api.PetUpdateNamePostDefStatusCode, error) {
 	panic("implement me")
 }
 
@@ -106,7 +106,7 @@ func (s sampleAPIServer) FoobarPut(ctx context.Context) (api.FoobarPutDefStatusC
 	panic("implement me")
 }
 
-func (s sampleAPIServer) FoobarPost(ctx context.Context, req api.Pet) (api.FoobarPostRes, error) {
+func (s sampleAPIServer) FoobarPost(ctx context.Context, req api.OptPet) (api.FoobarPostRes, error) {
 	panic("implement me")
 }
 
@@ -114,9 +114,9 @@ func (s sampleAPIServer) PetGet(ctx context.Context, params api.PetGetParams) (a
 	panic("implement me")
 }
 
-func (s *sampleAPIServer) PetCreate(ctx context.Context, req api.Pet) (pet api.Pet, err error) {
-	s.pet = req
-	return req, nil
+func (s *sampleAPIServer) PetCreate(ctx context.Context, req api.OptPet) (pet api.Pet, err error) {
+	s.pet = req.Value
+	return req.Value, nil
 }
 
 func (s *sampleAPIServer) PetGetByName(ctx context.Context, params api.PetGetByNameParams) (api.Pet, error) {
@@ -317,7 +317,7 @@ func TestIntegration(t *testing.T) {
 		}
 
 		t.Run("PetCreate", func(t *testing.T) {
-			got, err := client.PetCreate(ctx, pet)
+			got, err := client.PetCreate(ctx, api.NewOptPet(pet))
 			require.NoError(t, err)
 			assertPet(t, pet, got)