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

writeOnly field #425

Closed
adamaltman opened this Issue Jul 27, 2015 · 15 comments

Comments

Projects
None yet
10 participants
@adamaltman

adamaltman commented Jul 27, 2015

Swagger spec supports a readOnly field, but I can't find support for a writeOnly field (like one may use for a sensitive piece of information like a pan or credentials of some sort). Is there a reason for this omission?

@casualjim

This comment has been minimized.

Contributor

casualjim commented Jul 27, 2015

you can get this use case with an allOf definition

@slavcodev

This comment has been minimized.

slavcodev commented Jul 27, 2015

@casualjim what do you mean? Can you show an example, please?

@adamaltman

This comment has been minimized.

adamaltman commented Jul 28, 2015

I don't follow.

@webron

This comment has been minimized.

Member

webron commented Aug 3, 2015

Both readOnly and writeOnly can be achieved using composition. I don't recall the specific reason to not support writeOnly directly, but it was probably somewhere along not wanting to over complicate the spec.

@ePaul

This comment has been minimized.

Contributor

ePaul commented Apr 19, 2016

@adamaltman @slavcodev The idea mentioned by @casualjim (and @webron) is to have two schemas, one for the "write" use case, the other one for the read one, which are referenced by the operations (in parameter or response definition). In order to duplicate less code, you can have a third one with the fields which are common for both read and write, and then refer to this with an allOf reference. Using writeOnly and readOnly is only a shortcut for this.

definitions:
   CommonExample:
      type: object
      properties:
         readAndWrite:
            type: string
   ReadExample:
      allOf:
        - $ref: '#/definitions/CommonExample'
        - type: object
          properties:
             readOnlyProperty:
                type: string
   WriteExample:
      allOf:
        - $ref: '#/definitions/CommonExample'
        - type: object
          properties:
             writeOnlyProperty:
                type: string
paths:
   /example
     put:
        parameters:
           - in: body
             required: true
             schema:
                $ref: '#/definitions/WriteExample'
        ...
     get:
        ...
        responses:
           '200':
              schema:
                 $ref: '#/definitions/ReadExample'
@devigned

This comment has been minimized.

devigned commented May 21, 2016

I'm curious what the guidance would be if you have a PUT that has slightly different semantics between create and update. I've described a scenarios we are running into here: Azure/azure-rest-api-specs#307.

tldr; On create (PUT to URI x), a body parameter can be set, but on an update (PUT to URI x) the same body parameter can't be changed. Basically, we'd need have a slightly different schema for the update semantics than the create, but URIs must be unique.

Thoughts?

@wparad

This comment has been minimized.

wparad commented May 21, 2016

Sigh. Standard is use POST for create and PATCH/PUT for update.

Really depends on the schema that should get returned in the GET. If the schema object doesn't need that field, just exclude it from the PUT call. If that field is part of the schema, don't use PUT, use PATCH and specify which fields are required.

@darrelmiller

This comment has been minimized.

Member

darrelmiller commented May 21, 2016

@wparad Could you point me to the standard you are referring? My reading of RFC 7231, which defines HTTP semantics, is that both POST and PUT are valid ways of creating resources. The difference being whether you know the URL of the new resource in advance and whether you wish to make the creation idempotent.

@devigned Faced with your scenario I would be tempted to keep the same schema for both scenarios. Conceptually it is the same resource you are dealing with, just with different constraints based on its state. The same happens with resources that have status fields where only certain transitions are allowed. JSON Schema can only describe so much. Trying to return different schemas based on the current state is a slippery slope to a whole whackload of complexity. If you really, really wanted to have two different schemas then I would recommend creating two distinct resources, one for creating and one for updating, but that's a bit weird too.

With PUT operations there are many challenging scenarios when it comes to data elements that are impacted by server logic. Sometimes properties of a resource are generated by the server e.g. LastModifiedDate, links. It is a common question as to whether these should be included in a PUT request as they are not modifiable by the client. As I understand it, the "complete replacement" semantics of PUT can be interpreted fairly liberally and I tend to use the mantra of "completely replace everything that makes sense".

If a client were to try and change the "location" of resource, when that characteristic is something that can only be changed at the point of creation, I think you have two choices. You can 401 the request and say that you can't change that value, or you can just ignore invalid property, and process the rest of the changes. You could even do something crazy like return a Warning header to indicate that the change to location was ignored. Using the Prefer header with the strict/lenient property, you can even allow the client to choose which behaviour to allow.

If you had the luxury of changing the URL structure you could make the location part of the URL which would prevent this scenario from ever occurring because there is no concept of renaming a resource. You would be forced to delete and recreate it to move it to a new location, which is consistent with the actual implementation behaviour.

@wparad

This comment has been minimized.

wparad commented May 21, 2016

@darrelmiller (from RFC 7231)
4.1 Paragraph 4

Once defined, a standardized method ought to have the same semantics when applied to any resource

But even applied to the same resource, different outcomes with the same resource is ridiculous.

4.2.2 Paragraph 4
Although you can use PUT to create a resource, the mere fact that the representation of the resource is required to be different for subsequent calls directly violates

A request method is considered "idempotent" if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request.

In your case since the subsequent calls are required to have different representations depending on the already known existence of the resource.

I can see headers being different between requests of the nature which use PUT for create and update (or replace). Take into account the nature of a file sharing server, where all files all managed by PUT. The only likely required fields in addition to the representation would be the ETag using If-None-Match or If-Modified-Since, to prevent conflicts.

I think you figured out the jist of all of this in your response to Microsoft asinine api you mentioned in the Azure link.

An alternative approach I would take would be to create a generator/nested resource to identity the fields which are not idempotent. I think Microsoft here is abusing the REST and isn't following the RFC, but without really looking into the API, I can't know that for sure. The lack of idempotency seems to be the biggest issue with this problem.

@darrelmiller

This comment has been minimized.

Member

darrelmiller commented May 21, 2016

Once defined, a standardized method ought to have the same semantics when applied to any resource

But even applied to the same resource, different outcomes with the same resource is ridiculous.

@wparad I'm not exactly sure what you are trying to say here, because, by definition, resources vary over time, so different outcomes from the same resource is the natural behaviour. Resources are not stateless.

Fielding's dissertation says:

a resource R is a temporally varying membership function MR(t), which for time t maps to a set of entities, or values, which are equivalent.

Being conceptually equivalent allows for a fairly wide variation of the representations.

Although you can use PUT to create a resource, the mere fact that the representation of the resource is required to be different for subsequent calls directly violates [idempotency]

I think the desire to have different schemas is a strong indication that there should really be two different resources, as you suggested with generator/nested resources. I'm not sure it has anything to do with idempotency. It is important to realize that just because you PUT A when you GET A, the representation could be different than what you PUT. And also the response to two idempotent requests doesn't have to be the same. e.g. DELETE A -> 200 & DELETE A -> 404 is still idempotent behaviour.

I don't think we need to resort to making derogatory comments about APIs that people are building. I think the specific API is struggling with a fairly common problem, but MSFT have the challenge of trying to define metadata to drive tooling for a very large API surface area that is built by a large number of teams.

Personally, I haven't seen any violation of REST constraints or spec violation.

@devigned

This comment has been minimized.

devigned commented May 23, 2016

@darrelmiller, the response in the associated issue in our REST specs was greatly helpful. I wish we had the URI structure you described. It would have made things much easier.

Unfortunately, the ship has sailed on the versions of the APIs affected by this behavior. We may change future versions, but I still need to model these versions. I'd like to do that in the most community accepted way possible. If that is simply describing the schema and adding more information to the description, then that is what we will do.

We'd also like to provided generated clients with the best possible experience, thus we may need to introduce an extension to allow us to generate a create and an update method using the same verb / uri combination. As mentioned above, we should also incorporate some ETAG values as well to better describe / ensure this behavior of the client / API, but without the separated schema, the generated client will still push the consumer down the path of failure. If we make the extension, then I think we'd need to incorporate a way to specify the two schemas.

/cc @rjmax

@rjmax

This comment has been minimized.

rjmax commented May 23, 2016

Thanks all - perhaps a bit more context would be helpful. In the Azure API sets, we see that attributes generally follow one of four patterns:

  1. Write only - e.g. secrets, which can be set, but you wouldn't want to return to the client.
  2. Read only - e.g. server-generated values, such as created/modified timestamps
  3. Read + write once - immutable properties, such as resource geographical location
  4. Read + write many - I guess this is the normal case :)

In the case of #1, let's take the case of a VM password. We need the user to set it, but don't want to return the password back to the client. It would seem peculiar to model the password as a separate resource, since the password is of no standalone utility - its benefit is only in the context of that particular resource.

@webron

This comment has been minimized.

Member

webron commented Jul 21, 2016

Tackling PR: #741

@webron

This comment has been minimized.

Member

webron commented Feb 22, 2017

This has been added in #894.

@rvnath

This comment has been minimized.

rvnath commented Sep 8, 2018

New to Swagger here. Are these issues addressed in 3.x..?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment