Repository: deepmap/oapi-codegen
Pull request #547 (2022-04-07 11:22:34)

feat: support readOnly and writeOnly
https://swagger.io/docs/specification/data-models/data-types/#readonly-writeonly

> You can use the readOnly and writeOnly keywords to mark specific properties as read-only or write-only. This is useful, for example, when GET returns more properties than used in POST – you can use the same schema in both GET and POST and mark the extra properties as readOnly. readOnly properties are included in responses but not in requests, and writeOnly properties may be sent in requests but not in responses.
> If a readOnly or writeOnly property is included in the required list, required affects just the relevant scope – responses only or requests only. That is, read-only required properties apply to responses only, and write-only required properties – to requests only. 


Patch:
diff --git a/README.md b/README.md
index 3e0fba8..b7b9387 100644
--- a/README.md
+++ b/README.md
@@ -474,6 +474,10 @@ which help you to use the various OpenAPI 3 Authentication mechanism.
   will override any default value. This extended property isn't supported in all parts of
   OpenAPI, so please refer to the spec as to where it's allowed. Swagger validation tools will
   flag incorrect usage of this property.
+- `x-go-name`: specifies Go field name. It allows you to specify the field name for a schema, and
+  will override any default value. This extended property isn't supported in all parts of
+  OpenAPI, so please refer to the spec as to where it's allowed. Swagger validation tools will
+  flag incorrect usage of this property.
 - `x-oapi-codegen-extra-tags`: adds extra Go field tags to the generated struct field. This is
   useful for interfacing with tag based ORM or validation libraries. The extra tags that
   are added are in addition to the regular json tags that are generated. If you specify your 
diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go
index 49c8747..f1a1ca2 100644
--- a/cmd/oapi-codegen/oapi-codegen.go
+++ b/cmd/oapi-codegen/oapi-codegen.go
@@ -163,7 +163,7 @@ func main() {
 			errExit("error writing generated code to file: %s", err)
 		}
 	} else {
-		fmt.Println(code)
+		fmt.Print(code)
 	}
 }
 
@@ -180,6 +180,19 @@ func loadTemplateOverrides(templatesDir string) (map[string]string, error) {
 	}
 
 	for _, f := range files {
+		// Recursively load subdirectory files, using the path relative to the templates
+		// directory as the key. This allows for overriding the files in the service-specific
+		// directories (e.g. echo, chi, etc.).
+		if f.IsDir() {
+			subFiles, err := loadTemplateOverrides(path.Join(templatesDir, f.Name()))
+			if err != nil {
+				return nil, err
+			}
+			for subDir, subFile := range subFiles {
+				templates[path.Join(f.Name(), subDir)] = subFile
+			}
+			continue
+		}
 		data, err := ioutil.ReadFile(path.Join(templatesDir, f.Name()))
 		if err != nil {
 			return nil, err
diff --git a/go.mod b/go.mod
index b751be3..0a25777 100644
--- a/go.mod
+++ b/go.mod
@@ -8,10 +8,11 @@ require (
 	github.com/go-playground/validator/v10 v10.9.0 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219
+	github.com/google/uuid v1.3.0
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/labstack/echo/v4 v4.6.3
 	github.com/lestrrat-go/jwx v1.2.7
-	github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd
+	github.com/matryer/moq v0.2.5
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/stretchr/testify v1.7.0
 	github.com/ugorji/go v1.2.6 // indirect
diff --git a/go.sum b/go.sum
index 811158e..dfd6d1a 100644
--- a/go.sum
+++ b/go.sum
@@ -45,6 +45,8 @@ github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGS
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -81,8 +83,8 @@ github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmt
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd h1:HvFwW+cm9bCbZ/+vuGNq7CRWXql8c0y8nGeYpqmpvmk=
-github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
+github.com/matryer/moq v0.2.5 h1:BGQISyhl7Gc9W/gMYmAJONh9mT6AYeyeTjNupNPknMs=
+github.com/matryer/moq v0.2.5/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE=
 github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
@@ -120,6 +122,7 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
 github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -134,6 +137,7 @@ golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -169,6 +173,7 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiT
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 h1:K+NlvTLy0oONtRtkl1jRD9xIhnItbG2PiE7YOdjPb+k=
 golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
diff --git a/pkg/codegen/extension.go b/pkg/codegen/extension.go
index 89d85fa..28294b1 100644
--- a/pkg/codegen/extension.go
+++ b/pkg/codegen/extension.go
@@ -7,21 +7,29 @@ import (
 
 const (
 	extPropGoType    = "x-go-type"
+	extGoFieldName   = "x-go-name"
 	extPropOmitEmpty = "x-omitempty"
 	extPropExtraTags = "x-oapi-codegen-extra-tags"
 )
 
-func extTypeName(extPropValue interface{}) (string, error) {
+func extString(extPropValue interface{}) (string, error) {
 	raw, ok := extPropValue.(json.RawMessage)
 	if !ok {
 		return "", fmt.Errorf("failed to convert type: %T", extPropValue)
 	}
-	var name string
-	if err := json.Unmarshal(raw, &name); err != nil {
+	var str string
+	if err := json.Unmarshal(raw, &str); err != nil {
 		return "", fmt.Errorf("failed to unmarshal json: %w", err)
 	}
 
-	return name, nil
+	return str, nil
+}
+func extTypeName(extPropValue interface{}) (string, error) {
+	return extString(extPropValue)
+}
+
+func extParseGoFieldName(extPropValue interface{}) (string, error) {
+	return extString(extPropValue)
 }
 
 func extParseOmitEmpty(extPropValue interface{}) (bool, error) {
diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go
index 1ee521c..ca3ed46 100644
--- a/pkg/codegen/schema.go
+++ b/pkg/codegen/schema.go
@@ -67,6 +67,8 @@ type Property struct {
 	Schema         Schema
 	Required       bool
 	Nullable       bool
+	ReadOnly       bool
+	WriteOnly      bool
 	ExtensionProps *openapi3.ExtensionProps
 }
 
@@ -76,7 +78,9 @@ func (p Property) GoFieldName() string {
 
 func (p Property) GoTypeDef() string {
 	typeDef := p.Schema.TypeDecl()
-	if !p.Schema.SkipOptionalPointer && (!p.Required || p.Nullable) {
+	if !p.Schema.SkipOptionalPointer &&
+		(!p.Required || p.Nullable || p.ReadOnly || p.WriteOnly) {
+
 		typeDef = "*" + typeDef
 	}
 	return typeDef
@@ -259,6 +263,8 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
 					Required:       required,
 					Description:    description,
 					Nullable:       p.Value.Nullable,
+					ReadOnly:       p.Value.ReadOnly,
+					WriteOnly:      p.Value.WriteOnly,
 					ExtensionProps: &p.Value.ExtensionProps,
 				}
 				outSchema.Properties = append(outSchema.Properties, prop)
@@ -392,6 +398,8 @@ func resolveType(schema *openapi3.Schema, path []string, outSchema *Schema) erro
 		case "json":
 			outSchema.GoType = "json.RawMessage"
 			outSchema.SkipOptionalPointer = true
+		case "uuid":
+			outSchema.GoType = "openapi_types.UUID"
 		default:
 			// All unrecognized formats are simply a regular string.
 			outSchema.GoType = "string"
@@ -432,7 +440,15 @@ func GenFieldsFromProperties(props []Property) []string {
 			}
 			field += fmt.Sprintf("%s\n", StringToGoComment(p.Description))
 		}
-		field += fmt.Sprintf("    %s %s", p.GoFieldName(), p.GoTypeDef())
+
+		goFieldName := p.GoFieldName()
+		if _, ok := p.ExtensionProps.Extensions[extGoFieldName]; ok {
+			if extGoFieldName, err := extParseGoFieldName(p.ExtensionProps.Extensions[extGoFieldName]); err == nil {
+				goFieldName = extGoFieldName
+			}
+		}
+
+		field += fmt.Sprintf("    %s %s", goFieldName, p.GoTypeDef())
 
 		// Support x-omitempty
 		omitEmpty := true
@@ -444,7 +460,7 @@ func GenFieldsFromProperties(props []Property) []string {
 
 		fieldTags := make(map[string]string)
 
-		if p.Required || p.Nullable || !omitEmpty {
+		if (p.Required && !p.ReadOnly && !p.WriteOnly) || p.Nullable || !omitEmpty {
 			fieldTags["json"] = p.JsonFieldName
 		} else {
 			fieldTags["json"] = p.JsonFieldName + ",omitempty"
diff --git a/pkg/codegen/templates/gin/gin-register.tmpl b/pkg/codegen/templates/gin/gin-register.tmpl
index 5362dd7..cdd11e8 100644
--- a/pkg/codegen/templates/gin/gin-register.tmpl
+++ b/pkg/codegen/templates/gin/gin-register.tmpl
@@ -1,21 +1,26 @@
 // GinServerOptions provides options for the Gin server.
 type GinServerOptions struct {
     BaseURL string
-    Middlewares []MiddlewareFunc
+    Middlewares []gin.HandlerFunc
 }
 
+// keeping for backward compatibility
+type MiddlewareFunc = gin.HandlerFunc
+
 // RegisterHandlers creates http.Handler with routing matching OpenAPI spec.
-func RegisterHandlers(router *gin.Engine, si ServerInterface) *gin.Engine {
+func RegisterHandlers(router gin.IRouter, si ServerInterface) gin.IRouter {
   return RegisterHandlersWithOptions(router, si, GinServerOptions{})
 }
 
 // RegisterHandlersWithOptions creates http.Handler with additional options
-func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options GinServerOptions) *gin.Engine {
+func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) gin.IRouter {
 {{if .}}wrapper := ServerInterfaceWrapper{
 Handler: si,
-HandlerMiddlewares: options.Middlewares,
 }
 {{end}}
+
+router = router.Group("/", options.Middlewares...)
+
 {{range .}}
 router.{{.Method }}(options.BaseURL+"{{.Path | swaggerUriToGinUri }}", wrapper.{{.OperationId}})
 {{end}}
diff --git a/pkg/codegen/templates/gin/gin-wrappers.tmpl b/pkg/codegen/templates/gin/gin-wrappers.tmpl
index 6eb787c..0ddc686 100644
--- a/pkg/codegen/templates/gin/gin-wrappers.tmpl
+++ b/pkg/codegen/templates/gin/gin-wrappers.tmpl
@@ -1,11 +1,8 @@
 // ServerInterfaceWrapper converts contexts to parameters.
 type ServerInterfaceWrapper struct {
     Handler ServerInterface
-    HandlerMiddlewares []MiddlewareFunc
 }
 
-type MiddlewareFunc func(c *gin.Context)
-
 {{range .}}{{$opid := .OperationId}}
 
 // {{$opid}} operation middleware
@@ -165,10 +162,6 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
     {{end}}
   {{end}}
 
-  for _, middleware := range siw.HandlerMiddlewares {
-    middleware(c)
-  }
-
   siw.Handler.{{.OperationId}}(c{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
 }
 {{end}}
diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go
index f433f48..a7dc3cf 100644
--- a/pkg/codegen/utils.go
+++ b/pkg/codegen/utils.go
@@ -534,19 +534,45 @@ func SanitizeEnumNames(enumNames []string) map[string]string {
 	return sanitizedDeDup
 }
 
-// Converts a Schema name to a valid Go type name. It converts to camel case, and makes sure the name is
-// valid in Go
-func SchemaNameToTypeName(name string) string {
-	if name == "$" {
-		name = "DollarSign"
-	} else {
-		name = ToCamelCase(name)
-		// Prepend "N" to schemas starting with a number
-		if name != "" && unicode.IsDigit([]rune(name)[0]) {
-			name = "N" + name
+func typeNamePrefix(name string) (prefix string) {
+	for _, r := range name {
+		switch r {
+		case '$':
+			if len(name) == 1 {
+				return "DollarSign"
+			}
+		case '-':
+			prefix += "Minus"
+		case '+':
+			prefix += "Plus"
+		case '&':
+			prefix += "And"
+		case '~':
+			prefix += "Tilde"
+		case '=':
+			prefix += "Equal"
+		case '#':
+			prefix += "Hash"
+		case '.':
+			prefix += "Dot"
+		default:
+			// Prepend "N" to schemas starting with a number
+			if prefix == "" && unicode.IsDigit(r) {
+				return "N"
+			}
+
+			// break the loop, done parsing prefix
+			return
 		}
 	}
-	return name
+
+	return
+}
+
+// SchemaNameToTypeName converts a Schema name to a valid Go type name. It converts to camel case, and makes sure the name is
+// valid in Go
+func SchemaNameToTypeName(name string) string {
+	return typeNamePrefix(name) + ToCamelCase(name)
 }
 
 // According to the spec, additionalProperties may be true, false, or a
diff --git a/pkg/gin-middleware/oapi_validate.go b/pkg/gin-middleware/oapi_validate.go
index 82a885e..44075c3 100644
--- a/pkg/gin-middleware/oapi_validate.go
+++ b/pkg/gin-middleware/oapi_validate.go
@@ -72,6 +72,8 @@ func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) gin.
 		err := ValidateRequestFromContext(c, router, options)
 		if err != nil {
 			// note: i am not sure if this is the best way to handle this
+			// todo: the error message should be customizable by the user
+			c.Error(err)
 			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
 		}
 		c.Next()
@@ -124,6 +126,9 @@ func ValidateRequestFromContext(c *gin.Context, router routers.Router, options *
 			errorLines := strings.Split(e.Error(), "\n")
 			return fmt.Errorf("error in openapi3filter.RequestError: %s", errorLines[0])
 		case *openapi3filter.SecurityRequirementsError:
+			for _, e := range e.Errors {
+				c.Error(e)
+			}
 			return fmt.Errorf("error in openapi3filter.SecurityRequirementsError: %s", e.Error())
 		default:
 			// This should never happen today, but if our upstream code changes,
diff --git a/pkg/types/uuid.go b/pkg/types/uuid.go
new file mode 100644
index 0000000..a4fb36d
--- /dev/null
+++ b/pkg/types/uuid.go
@@ -0,0 +1,29 @@
+package types
+
+import (
+	"encoding/json"
+	"errors"
+
+	"github.com/google/uuid"
+)
+
+type UUID string
+
+func (u UUID) MarshalJSON() ([]byte, error) {
+	if _, err := uuid.Parse(string(u)); err != nil {
+		return nil, errors.New("uuid: failed to pass validation")
+	}
+	return json.Marshal(string(u))
+}
+
+func (u *UUID) UnmarshalJSON(data []byte) error {
+	var s string
+	if err := json.Unmarshal(data, &s); err != nil {
+		return err
+	}
+	if _, err := uuid.Parse(s); err != nil {
+		return errors.New("uuid: failed to pass validation")
+	}
+	*u = UUID(s)
+	return nil
+}


Test patch:
diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go
index 54f9eb2..fbb91f6 100644
--- a/internal/test/components/components.gen.go
+++ b/internal/test/components/components.gen.go
@@ -70,7 +70,12 @@ type ObjectWithJsonField struct {
 // SchemaObject defines model for SchemaObject.
 type SchemaObject struct {
 	FirstName string `json:"firstName"`
-	Role      string `json:"role"`
+
+	// This property is required and readOnly, so the go model should have it as a pointer,
+	// as it will not be included when it is sent from client to server.
+	ReadOnlyRequiredProp  *string `json:"readOnlyRequiredProp,omitempty"`
+	Role                  string  `json:"role"`
+	WriteOnlyRequiredProp *int    `json:"writeOnlyRequiredProp,omitempty"`
 }
 
 // ResponseObject defines model for ResponseObject.
@@ -1368,23 +1373,25 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"H4sIAAAAAAAC/9RXTW/jNhD9KwTboxLHzvaim4tu0RRoG+wG6GFjBLQ4ipjKQy05sldY6L8XQ8nflNdx",
-	"9rK5RJY5X49v3oy/yswuKouA5GX6VTr4XIOnX602EF582Lxo+GNmkQCJH1VVlSZTZCyOXrxFfuezAhaK",
-	"nypnK3DUe/ndQKn54WcHuUzlT6Nt2FFn5Ecfw/9/5i+QkWzbJCRjHGiZfuo9zPg1wRcaVaUyByGpqUCm",
-	"0pMz+Cxb/mMfvrLo18V0H/oYP1o9idTgM2cqzlGmciq8WVQliHWRwm6D9Vmwo6nWhk1Ueb+poktrHAqP",
-	"fL0T3yDBMzh5FP4P5cXWVmwREjYXbCwMkkwOoDM67hvVAiJVJ9JWXYAYJPuYBhcJR5gl66NrRJITKEyG",
-	"UchV6eGw8N8seIGWhCpLu4pj8Na6v1Npt8OlkauPKptyQV4obCJVNUc1vSL316X97nVpByaixWZhay9y",
-	"bi2xKkxWiGKIo8f3gwjuW2G/a/nnmXd5JZeg+Mup7j5fuc7v+5WhQnRORG6d0CYLh1wH+FHqXYR/DRV/",
-	"eosbUT0L5UQuVVlDULDcuoUimcqg28nA0ckZR+Nt10eKob8H1VHuuXGe/h4qwNnyDAKEU8mOq1kYBQZz",
-	"y8alyQA9bJGSf909sHcyxO7lA3gSH8EtA42W4Hx3jePrm+ubTmABVWVkKm+vb67H3BmKipD/CNDXDq5g",
-	"Ca6hwuDzlfFXDnJwgBmE23qGUPg+Rx4K4wWgrqxBEvDFePLCW0GFIrFlnMgUijmIzIEi0MKgoML4R/QV",
-	"ZEKhDjI7B1G5GkE/8o0xvmFK32mZyvchwfeb/O78h212yc4+0wyRfm/lGe3uO4frw+Tm5g07Q26W8K3G",
-	"O9XLbSJzW7vLXbxjFy+7jXbKT6w3mSz4hiLGgZeFgzf4uA0+VvZyD5PQYged3K9XuapLGmZKT4bRwSLZ",
-	"WY8q5dTCP7EKPimtn/j+/WCLTAW3WaeZwRIInA+kV2JuddOPsF4L4pIb6Yj7kAVf3FTr+5ACd/Q6gEw/",
-	"RZt1c2J4ZoZgvKXKzzW4Zj2UUlmN5a5mdbNy2wenJuqRoHpqgmx1q61sk3OyxZ3xHwbmZmfpQUQA7QVZ",
-	"MYdHpNphEBuyQvUnu4WVh1YsW7ZcWfffMAKTkwi8atWITIpDssZWhFnbzvYEC+uybBNZWR9hXxjiote+",
-	"XbqxuimD/K02DjKKApIwTx/xJPBM7JhthLMstweMdRf+8Dx/fTv3GnaW9Qt3uPX2vr6n9pAr7fHFtW37",
-	"fwAAAP//Xv36ip0PAAA=",
+	"H4sIAAAAAAAC/9RXTW/cNhD9KwO2R9nrj/SyNxdNURdoEzgGeogNgxZHFl3uUCEpb4RA/70YStqVtdRm",
+	"bedSX6ylyPl4M/P49E3kdlVZQgpeLL8Jh19q9OFXqzTGhavNQsM/c0sBKfCjrCqjcxm0pcWjt8RrPi9x",
+	"JfmpcrZCF3orv2s0ih9+dliIpfhpsXW76A75xaf4/8P9I+ZBtG0Wg9EOlVh+7i3c8nLAr2FRGaknLkNT",
+	"oVgKH5ymB9HyH9vwlSU/JNP96H383/LJhEKfO11xjGIpLsDrVWUQhiTBbp31UbChC6U0H5Hm4yaLLqzT",
+	"mHji9ci/poAP6MSO+z+kh+1Z2CIEtgA+DJqCyCbQaZW2TXKFiawzYavOQQqS55hGExl7uM2GrQMi2R4U",
+	"zuZRKKTxOE38N4seyAaQxth1GoO35v2DUjufTy24eiezC07Ig6QmkVWzk9MLYn9Z2O9eFnbsRLLUrGzt",
+	"oeDRgnWp8xLKuR7drQ8Ruu+5/aHpH3a8iyt7DYq/7Jvuw5nr8Llf61BCZwQK60DpPG5yHeA7oXce/tGh",
+	"/NNb2pDqQShn4kmaGiODFdatZBBLEXk7m9l6dsDW9Nj1nlLoP4NqJ/ZCOx/+nkvAoVQfyDRXvUcuEG98",
+	"Dvd1qf1mBEF7GAIESQoGGxl4C6FEeLCwsgoN+NLWRkEpnxB0AC4aVJaZx2VwQ9Lz6lobE6nsntk6N7VC",
+	"Hh0kfqk9eKQAhbMryI3m52DBo3tCd3zD8A3uhwHZzdGadPJrpwOmsp9Q5Ghn52Rao+ghG0E9A+ycy9t4",
+	"s2oqLHs3OkfyuG088dflNccbdOBMxDX6AJ8iBNwZ6HxXptPjk+OT7r5CkpUWS3F+fHJ8ykQjQxnbYYHk",
+	"a4dH+ISuCaWmhyPtjxwW6JByjM3/gGGmB5BUrB/gV+2D7wouA2wHGHJJXMncoQyoQBOEUvsb8hXmsV36",
+	"UleuJlSxgtxXUfRcKrEU72OA7zfxXfqrbXTZSB42cxzyTEEuxvJxqsbOTk7eIMEK/YTf47F91NhmorC1",
+	"e72Jd2ziccxb++ykqI6bhd6QxGnsy9LhG2ycRxtr+3oLZ5E1J8TYq9VC1ibMd0rfDIuJLu9OLyrp5Mrf",
+	"8aVyJ5W64/r72RG5AB6z7gqKJzGg87HpJdxb1fSKoKeO9A2WmIiPMQou3IWKhBGlw8aBWH5OE/awY16C",
+	"RGcs+sWXGl0z3PFLUZ2KMcV1zLqdg30CZed+8qGJtNV9KYg2OyRaGqmpqD+2908HIiEqz3fBPd5QqB1F",
+	"sgmWr5i4s9P/rAFS0fLJtXX/ziNwtheBFym3xN0zbdaU4rpt29tnhEW1MW0mKusT3Rc1EfTcN243Zjep",
+	"id8q7TAPSUAy7tMb2gs8N3bqbKJnmW4nHete+R1/uBo+tAyjb59XSuLhY2ioUzvtlXa3cG3b/hcAAP//",
+	"Jit1A+wQAAA=",
 }
 
 // GetSwagger returns the content of the embedded swagger specification file
diff --git a/internal/test/components/components.yaml b/internal/test/components/components.yaml
index 03456ed..479159e 100644
--- a/internal/test/components/components.yaml
+++ b/internal/test/components/components.yaml
@@ -92,9 +92,20 @@ components:
           type: string
         firstName:
           type: string
+        readOnlyRequiredProp:
+          description: |
+            This property is required and readOnly, so the go model should have it as a pointer, 
+            as it will not be included when it is sent from client to server.
+          type: string
+          readOnly: true
+        writeOnlyRequiredProp:
+          type: integer
+          writeOnly: true
       required:
         - role
         - firstName
+        - readOnlyRequiredProp
+        - writeOnlyRequiredProp
     AdditionalPropertiesObject1:
       description: Has additional properties of type int
       type: object
diff --git a/internal/test/server/server_moq.gen.go b/internal/test/server/server_moq.gen.go
index 0cc6afc..b4dde63 100644
--- a/internal/test/server/server_moq.gen.go
+++ b/internal/test/server/server_moq.gen.go
@@ -8,65 +8,52 @@ import (
 	"sync"
 )
 
-var (
-	lockServerInterfaceMockCreateResource           sync.RWMutex
-	lockServerInterfaceMockCreateResource2          sync.RWMutex
-	lockServerInterfaceMockGetEveryTypeOptional     sync.RWMutex
-	lockServerInterfaceMockGetReservedKeyword       sync.RWMutex
-	lockServerInterfaceMockGetResponseWithReference sync.RWMutex
-	lockServerInterfaceMockGetSimple                sync.RWMutex
-	lockServerInterfaceMockGetWithArgs              sync.RWMutex
-	lockServerInterfaceMockGetWithContentType       sync.RWMutex
-	lockServerInterfaceMockGetWithReferences        sync.RWMutex
-	lockServerInterfaceMockUpdateResource3          sync.RWMutex
-)
-
 // Ensure, that ServerInterfaceMock does implement ServerInterface.
 // If this is not the case, regenerate this file with moq.
 var _ ServerInterface = &ServerInterfaceMock{}
 
 // ServerInterfaceMock is a mock implementation of ServerInterface.
 //
-//     func TestSomethingThatUsesServerInterface(t *testing.T) {
+// 	func TestSomethingThatUsesServerInterface(t *testing.T) {
 //
-//         // make and configure a mocked ServerInterface
-//         mockedServerInterface := &ServerInterfaceMock{
-//             CreateResourceFunc: func(w http.ResponseWriter, r *http.Request, argument Argument)  {
-// 	               panic("mock out the CreateResource method")
-//             },
-//             CreateResource2Func: func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params)  {
-// 	               panic("mock out the CreateResource2 method")
-//             },
-//             GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request)  {
-// 	               panic("mock out the GetEveryTypeOptional method")
-//             },
-//             GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request)  {
-// 	               panic("mock out the GetReservedKeyword method")
-//             },
-//             GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request)  {
-// 	               panic("mock out the GetResponseWithReference method")
-//             },
-//             GetSimpleFunc: func(w http.ResponseWriter, r *http.Request)  {
-// 	               panic("mock out the GetSimple method")
-//             },
-//             GetWithArgsFunc: func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams)  {
-// 	               panic("mock out the GetWithArgs method")
-//             },
-//             GetWithContentTypeFunc: func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType)  {
-// 	               panic("mock out the GetWithContentType method")
-//             },
-//             GetWithReferencesFunc: func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument Argument)  {
-// 	               panic("mock out the GetWithReferences method")
-//             },
-//             UpdateResource3Func: func(w http.ResponseWriter, r *http.Request, pFallthrough int)  {
-// 	               panic("mock out the UpdateResource3 method")
-//             },
-//         }
+// 		// make and configure a mocked ServerInterface
+// 		mockedServerInterface := &ServerInterfaceMock{
+// 			CreateResourceFunc: func(w http.ResponseWriter, r *http.Request, argument Argument)  {
+// 				panic("mock out the CreateResource method")
+// 			},
+// 			CreateResource2Func: func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params)  {
+// 				panic("mock out the CreateResource2 method")
+// 			},
+// 			GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request)  {
+// 				panic("mock out the GetEveryTypeOptional method")
+// 			},
+// 			GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request)  {
+// 				panic("mock out the GetReservedKeyword method")
+// 			},
+// 			GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request)  {
+// 				panic("mock out the GetResponseWithReference method")
+// 			},
+// 			GetSimpleFunc: func(w http.ResponseWriter, r *http.Request)  {
+// 				panic("mock out the GetSimple method")
+// 			},
+// 			GetWithArgsFunc: func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams)  {
+// 				panic("mock out the GetWithArgs method")
+// 			},
+// 			GetWithContentTypeFunc: func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType)  {
+// 				panic("mock out the GetWithContentType method")
+// 			},
+// 			GetWithReferencesFunc: func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument Argument)  {
+// 				panic("mock out the GetWithReferences method")
+// 			},
+// 			UpdateResource3Func: func(w http.ResponseWriter, r *http.Request, pFallthrough int)  {
+// 				panic("mock out the UpdateResource3 method")
+// 			},
+// 		}
 //
-//         // use mockedServerInterface in code that requires ServerInterface
-//         // and then make assertions.
+// 		// use mockedServerInterface in code that requires ServerInterface
+// 		// and then make assertions.
 //
-//     }
+// 	}
 type ServerInterfaceMock struct {
 	// CreateResourceFunc mocks the CreateResource method.
 	CreateResourceFunc func(w http.ResponseWriter, r *http.Request, argument Argument)
@@ -187,6 +174,16 @@ type ServerInterfaceMock struct {
 			PFallthrough int
 		}
 	}
+	lockCreateResource           sync.RWMutex
+	lockCreateResource2          sync.RWMutex
+	lockGetEveryTypeOptional     sync.RWMutex
+	lockGetReservedKeyword       sync.RWMutex
+	lockGetResponseWithReference sync.RWMutex
+	lockGetSimple                sync.RWMutex
+	lockGetWithArgs              sync.RWMutex
+	lockGetWithContentType       sync.RWMutex
+	lockGetWithReferences        sync.RWMutex
+	lockUpdateResource3          sync.RWMutex
 }
 
 // CreateResource calls CreateResourceFunc.
@@ -203,9 +200,9 @@ func (mock *ServerInterfaceMock) CreateResource(w http.ResponseWriter, r *http.R
 		R:        r,
 		Argument: argument,
 	}
-	lockServerInterfaceMockCreateResource.Lock()
+	mock.lockCreateResource.Lock()
 	mock.calls.CreateResource = append(mock.calls.CreateResource, callInfo)
-	lockServerInterfaceMockCreateResource.Unlock()
+	mock.lockCreateResource.Unlock()
 	mock.CreateResourceFunc(w, r, argument)
 }
 
@@ -222,9 +219,9 @@ func (mock *ServerInterfaceMock) CreateResourceCalls() []struct {
 		R        *http.Request
 		Argument Argument
 	}
-	lockServerInterfaceMockCreateResource.RLock()
+	mock.lockCreateResource.RLock()
 	calls = mock.calls.CreateResource
-	lockServerInterfaceMockCreateResource.RUnlock()
+	mock.lockCreateResource.RUnlock()
 	return calls
 }
 
@@ -244,9 +241,9 @@ func (mock *ServerInterfaceMock) CreateResource2(w http.ResponseWriter, r *http.
 		InlineArgument: inlineArgument,
 		Params:         params,
 	}
-	lockServerInterfaceMockCreateResource2.Lock()
+	mock.lockCreateResource2.Lock()
 	mock.calls.CreateResource2 = append(mock.calls.CreateResource2, callInfo)
-	lockServerInterfaceMockCreateResource2.Unlock()
+	mock.lockCreateResource2.Unlock()
 	mock.CreateResource2Func(w, r, inlineArgument, params)
 }
 
@@ -265,9 +262,9 @@ func (mock *ServerInterfaceMock) CreateResource2Calls() []struct {
 		InlineArgument int
 		Params         CreateResource2Params
 	}
-	lockServerInterfaceMockCreateResource2.RLock()
+	mock.lockCreateResource2.RLock()
 	calls = mock.calls.CreateResource2
-	lockServerInterfaceMockCreateResource2.RUnlock()
+	mock.lockCreateResource2.RUnlock()
 	return calls
 }
 
@@ -283,9 +280,9 @@ func (mock *ServerInterfaceMock) GetEveryTypeOptional(w http.ResponseWriter, r *
 		W: w,
 		R: r,
 	}
-	lockServerInterfaceMockGetEveryTypeOptional.Lock()
+	mock.lockGetEveryTypeOptional.Lock()
 	mock.calls.GetEveryTypeOptional = append(mock.calls.GetEveryTypeOptional, callInfo)
-	lockServerInterfaceMockGetEveryTypeOptional.Unlock()
+	mock.lockGetEveryTypeOptional.Unlock()
 	mock.GetEveryTypeOptionalFunc(w, r)
 }
 
@@ -300,9 +297,9 @@ func (mock *ServerInterfaceMock) GetEveryTypeOptionalCalls() []struct {
 		W http.ResponseWriter
 		R *http.Request
 	}
-	lockServerInterfaceMockGetEveryTypeOptional.RLock()
+	mock.lockGetEveryTypeOptional.RLock()
 	calls = mock.calls.GetEveryTypeOptional
-	lockServerInterfaceMockGetEveryTypeOptional.RUnlock()
+	mock.lockGetEveryTypeOptional.RUnlock()
 	return calls
 }
 
@@ -318,9 +315,9 @@ func (mock *ServerInterfaceMock) GetReservedKeyword(w http.ResponseWriter, r *ht
 		W: w,
 		R: r,
 	}
-	lockServerInterfaceMockGetReservedKeyword.Lock()
+	mock.lockGetReservedKeyword.Lock()
 	mock.calls.GetReservedKeyword = append(mock.calls.GetReservedKeyword, callInfo)
-	lockServerInterfaceMockGetReservedKeyword.Unlock()
+	mock.lockGetReservedKeyword.Unlock()
 	mock.GetReservedKeywordFunc(w, r)
 }
 
@@ -335,9 +332,9 @@ func (mock *ServerInterfaceMock) GetReservedKeywordCalls() []struct {
 		W http.ResponseWriter
 		R *http.Request
 	}
-	lockServerInterfaceMockGetReservedKeyword.RLock()
+	mock.lockGetReservedKeyword.RLock()
 	calls = mock.calls.GetReservedKeyword
-	lockServerInterfaceMockGetReservedKeyword.RUnlock()
+	mock.lockGetReservedKeyword.RUnlock()
 	return calls
 }
 
@@ -353,9 +350,9 @@ func (mock *ServerInterfaceMock) GetResponseWithReference(w http.ResponseWriter,
 		W: w,
 		R: r,
 	}
-	lockServerInterfaceMockGetResponseWithReference.Lock()
+	mock.lockGetResponseWithReference.Lock()
 	mock.calls.GetResponseWithReference = append(mock.calls.GetResponseWithReference, callInfo)
-	lockServerInterfaceMockGetResponseWithReference.Unlock()
+	mock.lockGetResponseWithReference.Unlock()
 	mock.GetResponseWithReferenceFunc(w, r)
 }
 
@@ -370,9 +367,9 @@ func (mock *ServerInterfaceMock) GetResponseWithReferenceCalls() []struct {
 		W http.ResponseWriter
 		R *http.Request
 	}
-	lockServerInterfaceMockGetResponseWithReference.RLock()
+	mock.lockGetResponseWithReference.RLock()
 	calls = mock.calls.GetResponseWithReference
-	lockServerInterfaceMockGetResponseWithReference.RUnlock()
+	mock.lockGetResponseWithReference.RUnlock()
 	return calls
 }
 
@@ -388,9 +385,9 @@ func (mock *ServerInterfaceMock) GetSimple(w http.ResponseWriter, r *http.Reques
 		W: w,
 		R: r,
 	}
-	lockServerInterfaceMockGetSimple.Lock()
+	mock.lockGetSimple.Lock()
 	mock.calls.GetSimple = append(mock.calls.GetSimple, callInfo)
-	lockServerInterfaceMockGetSimple.Unlock()
+	mock.lockGetSimple.Unlock()
 	mock.GetSimpleFunc(w, r)
 }
 
@@ -405,9 +402,9 @@ func (mock *ServerInterfaceMock) GetSimpleCalls() []struct {
 		W http.ResponseWriter
 		R *http.Request
 	}
-	lockServerInterfaceMockGetSimple.RLock()
+	mock.lockGetSimple.RLock()
 	calls = mock.calls.GetSimple
-	lockServerInterfaceMockGetSimple.RUnlock()
+	mock.lockGetSimple.RUnlock()
 	return calls
 }
 
@@ -425,9 +422,9 @@ func (mock *ServerInterfaceMock) GetWithArgs(w http.ResponseWriter, r *http.Requ
 		R:      r,
 		Params: params,
 	}
-	lockServerInterfaceMockGetWithArgs.Lock()
+	mock.lockGetWithArgs.Lock()
 	mock.calls.GetWithArgs = append(mock.calls.GetWithArgs, callInfo)
-	lockServerInterfaceMockGetWithArgs.Unlock()
+	mock.lockGetWithArgs.Unlock()
 	mock.GetWithArgsFunc(w, r, params)
 }
 
@@ -444,9 +441,9 @@ func (mock *ServerInterfaceMock) GetWithArgsCalls() []struct {
 		R      *http.Request
 		Params GetWithArgsParams
 	}
-	lockServerInterfaceMockGetWithArgs.RLock()
+	mock.lockGetWithArgs.RLock()
 	calls = mock.calls.GetWithArgs
-	lockServerInterfaceMockGetWithArgs.RUnlock()
+	mock.lockGetWithArgs.RUnlock()
 	return calls
 }
 
@@ -464,9 +461,9 @@ func (mock *ServerInterfaceMock) GetWithContentType(w http.ResponseWriter, r *ht
 		R:           r,
 		ContentType: contentType,
 	}
-	lockServerInterfaceMockGetWithContentType.Lock()
+	mock.lockGetWithContentType.Lock()
 	mock.calls.GetWithContentType = append(mock.calls.GetWithContentType, callInfo)
-	lockServerInterfaceMockGetWithContentType.Unlock()
+	mock.lockGetWithContentType.Unlock()
 	mock.GetWithContentTypeFunc(w, r, contentType)
 }
 
@@ -483,9 +480,9 @@ func (mock *ServerInterfaceMock) GetWithContentTypeCalls() []struct {
 		R           *http.Request
 		ContentType GetWithContentTypeParamsContentType
 	}
-	lockServerInterfaceMockGetWithContentType.RLock()
+	mock.lockGetWithContentType.RLock()
 	calls = mock.calls.GetWithContentType
-	lockServerInterfaceMockGetWithContentType.RUnlock()
+	mock.lockGetWithContentType.RUnlock()
 	return calls
 }
 
@@ -505,9 +502,9 @@ func (mock *ServerInterfaceMock) GetWithReferences(w http.ResponseWriter, r *htt
 		GlobalArgument: globalArgument,
 		Argument:       argument,
 	}
-	lockServerInterfaceMockGetWithReferences.Lock()
+	mock.lockGetWithReferences.Lock()
 	mock.calls.GetWithReferences = append(mock.calls.GetWithReferences, callInfo)
-	lockServerInterfaceMockGetWithReferences.Unlock()
+	mock.lockGetWithReferences.Unlock()
 	mock.GetWithReferencesFunc(w, r, globalArgument, argument)
 }
 
@@ -526,9 +523,9 @@ func (mock *ServerInterfaceMock) GetWithReferencesCalls() []struct {
 		GlobalArgument int64
 		Argument       Argument
 	}
-	lockServerInterfaceMockGetWithReferences.RLock()
+	mock.lockGetWithReferences.RLock()
 	calls = mock.calls.GetWithReferences
-	lockServerInterfaceMockGetWithReferences.RUnlock()
+	mock.lockGetWithReferences.RUnlock()
 	return calls
 }
 
@@ -546,9 +543,9 @@ func (mock *ServerInterfaceMock) UpdateResource3(w http.ResponseWriter, r *http.
 		R:            r,
 		PFallthrough: pFallthrough,
 	}
-	lockServerInterfaceMockUpdateResource3.Lock()
+	mock.lockUpdateResource3.Lock()
 	mock.calls.UpdateResource3 = append(mock.calls.UpdateResource3, callInfo)
-	lockServerInterfaceMockUpdateResource3.Unlock()
+	mock.lockUpdateResource3.Unlock()
 	mock.UpdateResource3Func(w, r, pFallthrough)
 }
 
@@ -565,8 +562,8 @@ func (mock *ServerInterfaceMock) UpdateResource3Calls() []struct {
 		R            *http.Request
 		PFallthrough int
 	}
-	lockServerInterfaceMockUpdateResource3.RLock()
+	mock.lockUpdateResource3.RLock()
 	calls = mock.calls.UpdateResource3
-	lockServerInterfaceMockUpdateResource3.RUnlock()
+	mock.lockUpdateResource3.RUnlock()
 	return calls
 }
diff --git a/pkg/codegen/utils_test.go b/pkg/codegen/utils_test.go
index fd560be..b391852 100644
--- a/pkg/codegen/utils_test.go
+++ b/pkg/codegen/utils_test.go
@@ -337,3 +337,25 @@ func TestEscapePathElements(t *testing.T) {
 	p = "/foo/bar:baz"
 	assert.Equal(t, "/foo/bar%3Abaz", EscapePathElements(p))
 }
+
+func TestSchemaNameToTypeName(t *testing.T) {
+	t.Parallel()
+
+	for in, want := range map[string]string{
+		"$":            "DollarSign",
+		"$ref":         "Ref",
+		"no_prefix~+-": "NoPrefix",
+		"123":          "N123",
+		"-1":           "Minus1",
+		"+1":           "Plus1",
+		"@timestamp,":  "Timestamp",
+		"&now":         "AndNow",
+		"~":            "Tilde",
+		"_foo":         "Foo",
+		"=3":           "Equal3",
+		"#Tag":         "HashTag",
+		".com":         "DotCom",
+	} {
+		assert.Equal(t, want, SchemaNameToTypeName(in))
+	}
+}
diff --git a/pkg/types/uuid_test.go b/pkg/types/uuid_test.go
new file mode 100644
index 0000000..7f19aaa
--- /dev/null
+++ b/pkg/types/uuid_test.go
@@ -0,0 +1,51 @@
+package types
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestUUID_MarshalJSON_Fail(t *testing.T) {
+	testUUID := "this-is-not-a-uuid"
+	b := struct {
+		UUIDField UUID `json:"uuid"`
+	}{
+		UUIDField: UUID(testUUID),
+	}
+	_, err := json.Marshal(b)
+	assert.Error(t, err)
+}
+
+func TestUUID_MarshalJSON_Pass(t *testing.T) {
+	testUUID := "9cb14230-b640-11ec-b909-0242ac120002"
+	b := struct {
+		UUIDField UUID `json:"uuid"`
+	}{
+		UUIDField: UUID(testUUID),
+	}
+	jsonBytes, err := json.Marshal(b)
+	assert.NoError(t, err)
+	assert.JSONEq(t, `{"uuid":"9cb14230-b640-11ec-b909-0242ac120002"}`, string(jsonBytes))
+}
+
+func TestUUID_UnmarshalJSON_Fail(t *testing.T) {
+	jsonStr := `{"uuid":"this-is-not-a-uuid"}`
+	b := struct {
+		UUIDField UUID `json:"uuid"`
+	}{}
+	err := json.Unmarshal([]byte(jsonStr), &b)
+	assert.Error(t, err)
+}
+
+func TestUUID_UnmarshalJSON_Pass(t *testing.T) {
+	testUUID := UUID("9cb14230-b640-11ec-b909-0242ac120002")
+	jsonStr := `{"uuid":"9cb14230-b640-11ec-b909-0242ac120002"}`
+	b := struct {
+		UUIDField UUID `json:"uuid"`
+	}{}
+	err := json.Unmarshal([]byte(jsonStr), &b)
+	assert.NoError(t, err)
+	assert.Equal(t, testUUID, b.UUIDField)
+}