raml-org / raml-spec

RAML Specification
http://raml.org
3.87k stars 857 forks source link

Partial inheritance #687

Open jsamr opened 6 years ago

jsamr commented 6 years ago

Version 1.2

For PATCH methods using JSON Merge Patch (RFC7386), it would be extremely handy to inherit partially, which means that each and every inherited field gets the non-required ? attribute. Directly inherited — from direct ancestors in inheritance graph — and indirectly inherited properties will receive the same non-required attribute.

Minimal example

Given the Person definition:

Person:
  properties:
    firstName: string
    lastName: string
    birth: datetime
    job?: string
    wage:  number | null

and the PersonPatch definition partly inheriting from Person type:

PersonPatch:
  type: Person?

will result in the following expansion:

PersonPatch:
  type: object
  properties:
    firstName?: string
    lastName?: string
    birth?: datetime
    job?: string
    wage?: number | null

Partial Nesting

Partial inheritance only applies to the inherited fields, and not to the fields nested in those inherited fields.

Example

Given the WorkingStatus and Person definitions:

WorkingStatus:
  properties:
    job: string
    wage: number | null
Person:
  properties: 
    firstName: string
    lastName: string
    workingStatus: WorkingStatus

and the PersonPatch definition partly inheriting from Person type:

PersonPatch:
  type: Person?

will result in the following expansion:

PersonPatch:
  type: object
  properties:
    firstName?: string
    lastName?: string
    birth?: datetime
    workingStatus?:
      job: string
      wage: number | null

Note that workingStatus.job and workingStatus.wage remain required. To settle this limitation, we can define a new operator, ??, in which the inherited type is deeply partial.

Deeply partial example

Given the WorkingStatus and Person definitions:

WorkingStatus:
  properties:
    job: string
    wage: number | null
Person:
  properties: 
    firstName: string
    lastName: string
    workingStatus: WorkingStatus

and the PersonPatch definition partly inheriting from Person type:

PersonPatch:
  type: Person??

will result in the following expansion:

PersonPatch:
  type: object
  properties:
    firstName?: string
    lastName?: string
    birth?: datetime
    workingStatus?:
      job?: string
      wage?: number | null

Note that workingStatus.job and workingStatus.wage are not required anymore.

Multiple type inheritance

The implications are not yet fully clear to me, so there might be rewording to do here

Edits:

jsamr commented 6 years ago

EDIT Renamed to partial inheritance because it reminds me of Partial<T> typescript generic type definition:

type Partial<T> = {
    [P in keyof T]?: T[P];
};
LuisTCosta commented 6 years ago

The idea is interesting and seems really useful, but it doesn't seem well defined to me. Can you elaborate a little bit more on the syntax you are proposing, perhaps by expanding your example, specifying which properties of the type 'Person' would be inherited/required

jsamr commented 6 years ago

@LuisTCosta I have expanded the definition and will add edit versions on the header for traceability. Feel free to comment :-)

LuisTCosta commented 6 years ago

Ah, now I can clearly see the usefulness of Partial Inheritance. It would solve my own issue, https://github.com/raml-org/raml-spec/issues/684. As for the Multiple type inheritance problem, I'm not sure that by

When a type C inherits from [A, B?], and A and B have a conflicting field, the required attribute will prevail if present on A.

you meant that the conflicting field would be required because type A was inherited first, but I believe it would be better to make a conflicting field required if it is required in any non-partial inherited type, independently of the order of inheritance. Either way, it would be great to see the community's opinion on this.

jsamr commented 6 years ago

@LuisTCosta Actually, this is what I meant! The precedence of the required attribute is order-independent (I will make that clear). Conflicting types are merged according to the specification:

Multiple inheritance is allowed only if the sub-type is still a valid type declaration after inheriting all restrictions from its parent types.

So I would find sensible that required: true is an inherited order-independent restriction.

CoderSpinoza commented 6 years ago

Also in need of Partial Inheritance! Hope this gets added to spec soon. :)