diff --git a/json_schema/oneof.yaml b/json_schema/oneof.yaml new file mode 100644 index 0000000..caac2d2 --- /dev/null +++ b/json_schema/oneof.yaml @@ -0,0 +1,21 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: https://aep.dev/component.oneof.json +title: x-aep-oneof +description: | + Represents a mutual exclusion constraint over multiple properties. + + It is an error for any properties specified by a oneof to be required. +type: object +required: [properties] +additionalProperties: false +properties: + properties: + description: | + A set of properties that are mutually exclusive. + type: array + items: + type: string + required: + description: | + If set, exactly one property from the `properties` array must be present on the object. + type: string diff --git a/proto/aep-api/aep/api/oneof_behavior.proto b/proto/aep-api/aep/api/oneof_behavior.proto new file mode 100644 index 0000000..21dba0b --- /dev/null +++ b/proto/aep-api/aep/api/oneof_behavior.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package aep.api; + +option cc_enable_arenas = true; +option go_package = "aep.dev/api"; +option java_multiple_files = true; +option java_outer_classname = "OneOfBehaviorProto"; +option java_package = "dev.aep.api"; +option objc_class_prefix = "AEP"; + +import "google/protobuf/descriptor.proto"; + +// Used to specify the behavior of a `oneof`. +message OneOfBehavior { + // If set, one field within the `oneof` must be set. + // + // When unset, the `oneof` is implicitly optional (the default behavior in + // protobuf). + bool required = 1; +} \ No newline at end of file diff --git a/schemas/oneof.md b/schemas/oneof.md new file mode 100644 index 0000000..26645a1 --- /dev/null +++ b/schemas/oneof.md @@ -0,0 +1,65 @@ +# Oneof + +As used by the [AEPs](http://aep.dev), a `oneof` represents a set of mutually +exclusive fields. + +## Schema + +Rather than a generic schema for `oneof`, we provide common components +supplementing the language features of specific IDLs. Using these enables +uniform behavior (and JSON serialization) across IDLs. + +### protobuf + +Protobuf's built-in `oneof` supports mutual exclusion of fields. However, it +does not have a way for an API indicate that at least one of the fields must be +set. For this purpose, a protobuf `oneof` may be annotated with +[`aep.api.OneOfBehavior`](../proto/aep-api/aep/api/oneof_behavior.proto): + +```proto +message Document { + // ... + + // The owner of the document. + oneof owner { + // The user who owns the document. + string user = 1 [(google.api.resource_reference) = { + type: "apis.example.com/User", + }]; + + // The group that owns the document. + string group = 2 [(google.api.resource_reference) = { + type: "apis.example.com/Group", + }]; + } [(aep.api.OneOfBehavior) = {required: true}]; +} +``` + +### OAS + +OAS 3 has a concept called `oneOf`, but it has different semantics than the +`oneof` defined by this document; the OAS concept says that a single field must +match exactly one of multiple possible schemas, rather than specifying mutual +exclusion over multiple properties. + +In order to represent a `oneof` in OAS, in a way that produces JSON equivalent +to that of protobuf, we define an extension `x-aep-oneof`: + +```yaml +components: + schemas: + Document: + type: object + properties: + user: { type: string } + group: { type: string } + x-aep-oneof: + name: owner + properties: [user, group] + required: true +``` + +The `x-aep-oneof` extension is used to indicate that at least one of the fields +in the `oneOf` array must be set. + +**Note:** OAS extension not yet defined.