Repository: swaggest/openapi-go (go)

Issue:
writeOnly tags aren't handled by spec reflection
## Description
`readOnly` is supported as a tag on structs in reflection. I'd expect that `writeOnly` would also be supported, but it doesn't appear to be (see reproduction).

## Reproduction
https://go.dev/play/p/gQ5lvj7oTeL

## Expected
I expected that the `writeOnly` tag would be present on the field on which the associated tag was added, in the same manner as `readOnly`.


Pull request:
Enable writeOnly and test readOnly and deprecated
Fixes #145 
Depends on swaggest/jsonschema-go#125

## Description
See #145. `writeOnly` wasn't being properly reflected in openAPI schemas generated by this library because of issues in `jsonschema-go`.

## TODO
- [ ] Merge swaggest/jsonschema-go#125 and update this PR to point at that ref


## Changes
* Use new code in jsonschema-go
* Change references to `deprecated` to use new field on `Schema` object rather than reading `ExtraProperties`
* Add test that ensures that `writeOnly`, `readOnly`, and `deprecated` are properly reflected

## Testing
Review. See that tests pass.

Patch:
diff --git a/.gitignore b/.gitignore
index ed77e49..0dd7783 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@
 /.vscode
 /bench-*.txt
 /vendor
+go.work
+go.work.sum
diff --git a/go.mod b/go.mod
index ff3967c..615ab27 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/bool64/dev v0.2.39
 	github.com/stretchr/testify v1.8.2
 	github.com/swaggest/assertjson v1.9.0
-	github.com/swaggest/jsonschema-go v0.3.73
+	github.com/swaggest/jsonschema-go v0.3.74
 	github.com/swaggest/refl v1.3.1
 	gopkg.in/yaml.v2 v2.4.0
 )
diff --git a/go.sum b/go.sum
index 6233c49..18c42e7 100644
--- a/go.sum
+++ b/go.sum
@@ -33,8 +33,8 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
 github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
-github.com/swaggest/jsonschema-go v0.3.73 h1:gU1pBzF3pkZ1GDD3dRMdQoCjrA0sldJ+QcM7aSSPgvc=
-github.com/swaggest/jsonschema-go v0.3.73/go.mod h1:qp+Ym2DIXHlHzch3HKz50gPf2wJhKOrAB/VYqLS2oJU=
+github.com/swaggest/jsonschema-go v0.3.74 h1:hkAZBK3RxNWU013kPqj0Q/GHGzYCCm9WcUTnfg2yPp0=
+github.com/swaggest/jsonschema-go v0.3.74/go.mod h1:qp+Ym2DIXHlHzch3HKz50gPf2wJhKOrAB/VYqLS2oJU=
 github.com/swaggest/refl v1.3.1 h1:XGplEkYftR7p9cz1lsiwXMM2yzmOymTE9vneVVpaOh4=
 github.com/swaggest/refl v1.3.1/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA=
 github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
diff --git a/openapi3/jsonschema.go b/openapi3/jsonschema.go
index 7d0149d..1cf4b82 100644
--- a/openapi3/jsonschema.go
+++ b/openapi3/jsonschema.go
@@ -164,6 +164,8 @@ func (s *SchemaOrRef) toJSONSchema(ctx toJSONSchemaContext) jsonschema.SchemaOrB
 	jso.Format = ss.Format
 	jso.Default = ss.Default
 	jso.ReadOnly = ss.ReadOnly
+	jso.WriteOnly = ss.WriteOnly
+	jso.Deprecated = ss.Deprecated
 
 	if ss.Example != nil {
 		jso.WithExamples(*ss.Example)
@@ -186,7 +188,7 @@ func (s *SchemaOrRef) FromJSONSchema(schema jsonschema.SchemaOrBool) {
 
 	js := schema.TypeObject
 	if js.Ref != nil {
-		if deprecated, ok := js.ExtraProperties["deprecated"].(bool); ok && deprecated {
+		if js.Deprecated != nil && *js.Deprecated {
 			s.Schema = (&Schema{}).WithAllOf(
 				SchemaOrRef{
 					Schema: (&Schema{}).WithDeprecated(true),
@@ -230,9 +232,7 @@ func (s *SchemaOrRef) FromJSONSchema(schema jsonschema.SchemaOrBool) {
 		os.Example = &js.Examples[0]
 	}
 
-	if deprecated, ok := js.ExtraProperties["deprecated"].(bool); ok {
-		os.Deprecated = &deprecated
-	}
+	os.Deprecated = js.Deprecated
 
 	if js.Type != nil {
 		if js.Type.SimpleTypes != nil {
@@ -309,12 +309,9 @@ func (s *SchemaOrRef) FromJSONSchema(schema jsonschema.SchemaOrBool) {
 	}
 
 	os.ReadOnly = js.ReadOnly
+	os.WriteOnly = js.WriteOnly
 	os.UniqueItems = js.UniqueItems
 
-	if writeOnly, ok := js.ExtraProperties["writeOnly"].(bool); ok {
-		os.WriteOnly = &writeOnly
-	}
-
 	for name, val := range js.ExtraProperties {
 		if strings.HasPrefix(name, "x-") {
 			if os.MapOfAnything == nil {
diff --git a/openapi31/jsonschema.go b/openapi31/jsonschema.go
index 77ad222..62429f7 100644
--- a/openapi31/jsonschema.go
+++ b/openapi31/jsonschema.go
@@ -11,11 +11,7 @@ func isDeprecated(schema jsonschema.SchemaOrBool) *bool {
 		return nil
 	}
 
-	if d, ok := schema.TypeObject.ExtraProperties["deprecated"].(bool); ok {
-		return &d
-	}
-
-	return nil
+	return schema.TypeObject.Deprecated
 }
 
 type toJSONSchemaContext struct {


Test patch:
diff --git a/openapi3/reflect_test.go b/openapi3/reflect_test.go
index 0defcd0..bfc7dd2 100644
--- a/openapi3/reflect_test.go
+++ b/openapi3/reflect_test.go
@@ -1461,3 +1461,59 @@ func TestSelfReference(t *testing.T) {
 	  }
 	}`, reflector.SpecSchema())
 }
+
+func TestReadOnlyWriteOnlyDeprecated(t *testing.T) {
+	type ExampleObject struct {
+		Foo string `json:"foo" readOnly:"true"`
+		Bar string `json:"bar" writeOnly:"true"`
+		Baz string `json:"baz" deprecated:"true"`
+	}
+
+	type ExampleResponse struct {
+		ExampleObject `json:"example"`
+	}
+
+	type ExampleRequest struct {
+		ExampleObject `json:"example"`
+	}
+
+	reflector := openapi3.NewReflector()
+	op, err := reflector.NewOperationContext("GET", "/some/path")
+	require.NoError(t, err)
+
+	op.AddReqStructure(ExampleRequest{})
+	op.AddRespStructure(ExampleResponse{})
+	require.NoError(t, reflector.AddOperation(op))
+
+	assertjson.EqMarshal(t, `{
+	  "openapi":"3.0.3","info":{"title":"","version":""},
+	  "paths":{
+	    "/some/path":{
+	      "get":{
+		"responses":{
+		  "200":{
+		    "description":"OK",
+		    "content":{
+		      "application/json":{
+			"schema":{"$ref":"#/components/schemas/Openapi3TestExampleResponse"}
+		      }
+		    }
+		  }
+		}
+	      }
+	    }
+	  },
+	  "components":{
+	    "schemas":{
+	      "Openapi3TestExampleObject":{
+		"type":"object",
+		"properties":{"bar":{"type":"string", "writeOnly": true},"baz":{"type":"string", "deprecated": true},"foo":{"type":"string","readOnly":true}}
+	      },
+	      "Openapi3TestExampleResponse":{
+		"type":"object",
+		"properties":{"example":{"$ref":"#/components/schemas/Openapi3TestExampleObject"}}
+	      }
+	    }
+	  }
+	}`, reflector.SpecSchema())
+}