raml-spec icon indicating copy to clipboard operation
raml-spec copied to clipboard

Use an !include in a union

Open sheamunion opened this issue 6 years ago • 6 comments

I want to create a required, nullable property with a user-defined type in a separate file. How can I accomplish this?

Response.raml

#%RAML 1.0 DataType

type: object
properties:
  error: nil | !include Error.raml

Error.raml

#%RAML 1.0 DataType

type: object
properties:
  code: string
  message: string

sheamunion avatar Jun 25 '19 21:06 sheamunion

!includes cannot participate in a union so your example is invalid.

There are several ways to achieve this depending on the structure of your RAML definition(s), one of them is to use a RAML library, e.g.:

Library.raml:

#%RAML 1.0 Library

types:
  Error: !include Error.raml
  Response:
    type: object
      properties:
        error: Error?

Error.raml:

#%RAML 1.0 DataType

type: object
properties:
  code: string
  message: string

PS: Note that error: Error? is equivalent to error: Error | nil. error: <anyTypeHere> also implies that the error property MUST be present regardless of its value. If what you intend is to make the error property optional, then you'll want to write error?: Error instead.

jstoiko avatar Jul 23 '19 20:07 jstoiko

I also miss the possibility to do an include in an Union, especially when building datatypes where a value must be either another datatype or nil.

Either this could be managed by accepting nil | !include Type.raml or a nullable property added to the field, similar to the requiredproperty.

GauthierPLM avatar Jul 24 '20 10:07 GauthierPLM

I don't see much of a limitation here since any type can be first defined (in that case by !includeing) it and later re-used in an union. Also, nullable can be expressed either using the | nil syntax of the ? (trailing question mark) syntax.

jstoiko avatar Jul 24 '20 16:07 jstoiko

May I ask how would you do this so currency can be null or the type Currency?

#%RAML 1.0 DataType
type: object
additionalProperties: false
properties:
  id: integer
  entityId: integer
  entityName: string
  managingEntityId: integer?
  currencyCode: string
  currency: any # !include Currency.raml | nil

GauthierPLM avatar Jul 24 '20 16:07 GauthierPLM

You can either turn your fragment into a RAML Library:

#%RAML 1.0 Library
types:
  Currency:
  MyType:
    type: object
    additionalProperties: false
    properties:
      id: integer
      entityId: integer
      entityName:
      managingEntityId: integer?
      currencyCode:
      currency: Currency? # equivalent to: Currency | nil

or use an external library in which Currency type would be defined:

#%RAML 1.0 DataType

uses: 
  lib: myLibrary.raml

type: object
additionalProperties: false
properties:
  id: integer
  entityId: integer
  entityName: string
  managingEntityId: integer?
  currencyCode: string
  currency: lib.Currency? # equivalent to: lib.Currency | nil

jstoiko avatar Jul 24 '20 22:07 jstoiko

After your suggestions, I played a bit with my RAML definitions and used both suggestions. Here are my thoughts:

  • Declaring the type as a library (your first example) has two disadvantages:
    • If we choose to include all types in the library file, the file might become very long (Salesforce objects for example) and would break the factorisation of the definitions.
    • or we only include one type per library file, which would defeat the purpose of having both libraries and datatypes. I see libraries as a way to put together multiples types and make them easily importable. I use this to bring all files in the same folder and import them together.
  • including a library in the DataType with uses can create cyclic dependencies if the datatypes is included in the library. For example (see code below), if I have a datatype Address.raml, another datatype Customer.raml with a field of type Address, and both types are used in the library.raml file, importing the library will create a cyclic dependency as Customer.raml will be included in itself when using library.raml.

Currently, the only solution I found is to use a type any for my field instead of including the type/using a library, especially If I do not want to create multiple libraries included in each other, defining types directly in the library, or defining the type inline (defining the Address type in the address field of Customer.raml).

The solutions I can imagine are:

  • The possibility to nullify a type via an additional property nullable (similar to required). This would allow to explicitly declare a type nullable, including a type included.
  • The possibility to have a types field in a datatype definition or allow uses to import other datatypes in a datatype declaration. This would not only cover nullability, but also union of includes as the included type is imported before the type definition.
  • The possibility to have an union on the same line as an include : !include Address.raml | nil, but this might become messy, especially if multiple !include are brought together.

Here are the example types to illustrate the cyclic dependencies:

Address.raml

#%RAML 1.0 DataType
type: object
properties:
  street: string

Customer.raml

#%RAML 1.0 DataType
uses:
  library: library.raml
type: object
properties:
  name: string
  address: library.Address | nil

library.raml

#%RAML 1.0 Library
types:
  Address: !include Address.raml
  Customer: !include Customer.raml

GauthierPLM avatar Sep 04 '20 16:09 GauthierPLM