Skip to content
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

W-16571553: merge avro feature to develop in amf #2038

Merged
merged 39 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d010887
W-16459888: add AvroRawSchema Annotation
arielmirra Aug 19, 2024
1b882c1
W-16459888: add Avro JVM and JS plugins
arielmirra Aug 19, 2024
689f973
W-16459888: split avro/jsonschema validation logic
arielmirra Aug 19, 2024
9fd56fb
W-16459888: initial validation testing
arielmirra Aug 19, 2024
afd5321
W-16540082: trow JVM-specific validation messages
arielmirra Aug 22, 2024
0b9c12c
W-16540082: simplify avro payload validators
arielmirra Aug 22, 2024
991581f
W-16540082: improve JS avro payload validation
arielmirra Aug 26, 2024
c45e9ae
W-15633281: add JVM schema validation tests
arielmirra Aug 28, 2024
71e1790
W-15633281: add JS AVRO Schema validation dummy code
arielmirra Aug 28, 2024
4b0b616
W-15633281: fix avro plugin validation error message
arielmirra Aug 28, 2024
336182a
W-16540082: stop serializing AVRORawSchema annotation
arielmirra Aug 29, 2024
218d46c
W-16609870: make async message payload object be virtual unless expli…
arielmirra Sep 2, 2024
9a1cc38
return an empty payload when parsing invalid avro
arielmirra Sep 6, 2024
c6320ad
W-16540082: rename avro Document Emitter
arielmirra Sep 12, 2024
271fd5c
W-16540082: fix Async AVRO payload emission
arielmirra Sep 12, 2024
50ae9da
W-16540082: improve AVRO adr doc
arielmirra Sep 12, 2024
af08613
W-16540082: improve base avro parser interfaces
arielmirra Sep 16, 2024
d648060
W-16540082: improve avro validation exceptions in JVM
arielmirra Sep 16, 2024
28050a0
W-16540082: make AVRORawSchema ann. Eternal
arielmirra Sep 16, 2024
17522ef
W-16540082: add YAML to avro validator mediaTypes
arielmirra Sep 16, 2024
0c7ad8e
add definedBySpec annotation to avro schemas in async apis
arielmirra Sep 18, 2024
24628be
W-16596042: fix avro map with empty values lexical
arielmirra Sep 18, 2024
788d555
W-16596042: update avro ADR validation constraints
arielmirra Sep 18, 2024
f3ff9cf
W-16701554: test avro default values validations
arielmirra Sep 18, 2024
2895299
W-16702592 - AVRO Js lib working
looseale Sep 19, 2024
9e6ff09
generalize loadDataNodeString method in avro validators
arielmirra Sep 19, 2024
cd6365f
fix avro collection members parsing for ALS
arielmirra Sep 19, 2024
16eb6da
W-16701643: add AvroSchemaPayloadValidationPlugin
arielmirra Sep 20, 2024
40bfea4
W-16702592 - Extracted AVRO Error filter. Fixed invalid root type val…
looseale Sep 20, 2024
fe56ec1
W-16701643: remove duplicated anns. in avro record range fields
arielmirra Sep 20, 2024
cdeb1a4
W-16702592 - Fix schema parse validation. Remove excluded errors (we …
looseale Sep 20, 2024
6c381ee
W-16702592 - Uncomment some tests that was ignored for JS
looseale Sep 20, 2024
c811e8f
W-16701643: put avro record field default value inside the property r…
arielmirra Sep 20, 2024
50f159a
W-16701643: test async+avro payload validation in channel message exa…
arielmirra Sep 20, 2024
b42a569
W-16702592 - Added custom NameValidator to JVM AVRO parser in order t…
looseale Sep 20, 2024
d2e0f40
W-16701643: test async+avro payload validator creation
arielmirra Sep 20, 2024
28e11b1
update avro ADR
arielmirra Sep 22, 2024
b019d9e
W-16701643: add anns. to empty avro type
arielmirra Sep 22, 2024
09004eb
update avro ADR
arielmirra Sep 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 0 additions & 59 deletions adrs/0014-avro-parsing.md

This file was deleted.

86 changes: 86 additions & 0 deletions adrs/0014-avro-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 14. AVRO Support in AMF

Date: 2024-07-02


## Status

Accepted


## Context

Async 2.x supports AVRO Schemas, and we currently don't.
We want to add support of AVRO Schemas:
- inside Async APIs
- as a standalone document

We need to decide:
- how are we going to map AVRO Schemas to the AMF Model
- how are we going to validate AVRO Schemas

an AVRO Schema has the following properties:
- It's defined in plain JSON, they MAY be also be defined as a `.avsc` file
- It doesn't have a special key that indicates it's an AVRO Schema, nor it's version (like JSON Schema does with it's `$schema`)


## Decision

Implement AVRO Schema parsing as a new specification, following the [AVRO Schema 1.9.0 specification](https://avro.apache.org/docs/1.9.0/spec.html#schemas).

An AVRO Schema may be a:
- Map
- Array
- Record (with fields, each one being any of the possible types)
- Enum
- Fixed Type
- Primitive Type ("null", "boolean", "int", "long", "float", "double", "bytes", "string")

We've parsed each AVRO Type to the following AMF Shape:
- Map --> NodeShape with `AdditionalProperties` field for the values shape
- Array --> ArrayShape with `Items` field for the items shape
- Record --> NodeShape with `Properties` with a PropertyShape that contains each field shape
- Enum --> ScalarShape with `Values` field for it's symbols
- Fixed Type --> ScalarShape with `Datatype` field for its type and `Size` for its size
- Primitive Type --> ScalarShape with `Datatype` field, or NilShape if its type 'null'

Given that in this mapping, several AVRO Types correspond to a ScalarShape or a NodeShape, **we've added the `avro-schema` annotation** with an `avroType` that contains the avro type declared before parsing.
This way, we can know the exact type for rendering or other purposes, for example having a NodeShape and knowing if it's an avro record or a map (both are parsed as NodeShapes).

We've also added 3 AVRO-specific fields to the `AnyShape` Model via the `AvroFields` trait, adding the following fields:
- AvroNamespace
- Aliases
- Size

### Where we support and DON'T support AVRO Schemas
We Support AVRO Schemas (inline or inside a `$ref`):
- as a standalone document or file
- we encourage users to use the `.avsc` file type to indicate that's an avro file, for better suggestions and so on in the platform)
- must use the specific `AvroConfiguration`
- inside a message payload in an AsyncAPI
- the key `schemaFormat` MUST be declared and specify it's an AVRO payload
arielmirra marked this conversation as resolved.
Show resolved Hide resolved
- we only support avro schema version 1.9.0
- the avro specific document `AvroSchemaDocument` can only be parsed with the specific `AvroConfiguration`

We don't support AVRO Schemas:
- inside components --> schemas in an AsyncAPI
- because we can't determine if it's an AVRO Schema or any other schema

### AVRO Validation
We'll use the Apache official libraries for JVM and JS.

## Consequences / Constraints

The validation plugins differ in interfaces and implementations, and each has some constraints:

### JVM avro validation plugin
- validation per se is not supported, we try to parse an avro schema and throw parsing results if there are any
- this means it's difficult to have location of where the error is thrown, we may give an approximate location from our end post-validation
- when a validation is thrown, the rest of the file is not being searched for more validations
- this is particularly important in large avro schemas, where many errors can be found but only one is shown

### Both JVM & JS validation plugins
- `"default"` values are not being validated when the type is `bytes`, `map`, or `array`
- the validator treats as invalid an empty array as the default value for arrays (`"default": []`) even though the [Avro Schema Specification](https://avro.apache.org/docs/1.12.0/specification) has some examples with it
arielmirra marked this conversation as resolved.
Show resolved Hide resolved
- if an avro record has a field that is a union that includes the root record itself (recursive reference) we fail to validate it correctly because we treat that shape as an unresolved/undefined shape
- in the future we'll try to ignore the cases that we are now failing and/or show a warning instead
29 changes: 28 additions & 1 deletion amf-antlr-syntax/js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion amf-antlr-syntax/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"@aml-org/amf-antlr-parsers": "0.7.25",
"ajv": "6.12.6"
"ajv": "6.12.6",
"avro-js": "1.12.0"
}
}
29 changes: 28 additions & 1 deletion amf-api-contract/js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion amf-api-contract/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"@aml-org/amf-antlr-parsers": "0.7.25",
"ajv": "6.12.6"
"ajv": "6.12.6",
"avro-js": "^1.12.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import amf.apicontract.internal.spec.avro.transformation.{
AvroSchemaEditingPipeline,
AvroSchemaTransformationPipeline
}
import amf.apicontract.internal.spec.avro.validation.AvroSchemaPayloadValidationPlugin
import amf.apicontract.internal.spec.avro.{AvroParsePlugin, AvroRenderPlugin}
import amf.apicontract.internal.spec.oas._
import amf.apicontract.internal.spec.raml._
Expand Down Expand Up @@ -49,10 +50,12 @@ import amf.core.internal.validation.EffectiveValidations
import amf.core.internal.validation.core.ValidationProfile
import amf.shapes.client.scala.ShapesConfiguration
import amf.shapes.client.scala.config.JsonSchemaConfiguration
import amf.shapes.client.scala.plugin.JsonSchemaShapePayloadValidationPlugin
import amf.shapes.client.scala.plugin.{AvroSchemaShapePayloadValidationPlugin, JsonSchemaShapePayloadValidationPlugin}
import amf.shapes.internal.annotations.ShapeSerializableAnnotations
import amf.shapes.internal.entities.ShapeEntities
import amf.shapes.internal.spec.jsonschema.JsonSchemaParsePlugin
import amf.shapes.internal.validation.model.ShapeEffectiveValidations.AvroSchemaEffectiveValidations
import amf.shapes.internal.validation.model.ShapeValidationProfiles.AvroSchemaValidationProfile

import scala.concurrent.Future

Expand Down Expand Up @@ -82,7 +85,8 @@ trait APIConfigurationBuilder {
configuration.idAdopterProvider
).withPlugins(
List(
JsonSchemaShapePayloadValidationPlugin
JsonSchemaShapePayloadValidationPlugin,
AvroSchemaShapePayloadValidationPlugin
)
).withFallback(ApiContractFallbackPlugin())
result
Expand Down Expand Up @@ -166,14 +170,21 @@ object RAMLConfiguration extends APIConfigurationBuilder {
object AvroConfiguration extends APIConfigurationBuilder {
def Avro(): AMFConfiguration = {
common()
.withPlugins(List(AvroParsePlugin, AvroRenderPlugin)) // TODO: add validation profiles
.withPlugins(
List(
AvroParsePlugin,
AvroRenderPlugin,
AvroSchemaPayloadValidationPlugin()
)
)
.withTransformationPipelines(
List(
AvroSchemaTransformationPipeline(),
AvroSchemaEditingPipeline(),
AvroSchemaCachePipeline()
)
)
.withValidationProfile(AvroSchemaValidationProfile, AvroSchemaEffectiveValidations)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package amf.apicontract.internal.spec.async.emitters.domain

import amf.apicontract.internal.spec.async.emitters.context.Async20SpecEmitterContext
import amf.apicontract.internal.spec.async.parser.domain.AsyncSchemaFormats
import amf.apicontract.internal.spec.avro.emitters.context.AvroShapeEmitterContext
import amf.apicontract.internal.spec.avro.emitters.domain.AvroShapeEmitter
import amf.apicontract.internal.spec.oas.emitter.context.{OasLikeShapeEmitterContextAdapter, OasLikeSpecEmitterContext}
import amf.apicontract.internal.spec.raml.emitter
import amf.apicontract.internal.spec.spec.toRaml
import org.mulesoft.common.client.lexical.Position
import amf.core.client.scala.model.document.BaseUnit
import amf.core.client.scala.model.domain.Shape
import amf.core.internal.render.BaseEmitters.pos
import amf.core.internal.render.BaseEmitters.{pos, traverse}
import amf.core.internal.render.SpecOrdering
import amf.core.internal.render.emitters.EntryEmitter
import amf.shapes.internal.spec.common.{AVROSchema, RAML10SchemaVersion, SchemaVersion}
import amf.shapes.internal.spec.oas.emitter.OasTypePartEmitter
import amf.shapes.internal.spec.raml.emitter.Raml10TypeEmitter
import org.mulesoft.common.client.lexical.Position
import org.yaml.model.YDocument.EntryBuilder

case class AsyncSchemaEmitter(
Expand Down Expand Up @@ -59,9 +61,9 @@ case class AsyncSchemaEmitter(
key,
b => {
val newCtx = new Async20SpecEmitterContext(spec.eh, config = spec.renderConfig, schemaVersion = schemaVersion)
// todo: call a specific AVRO Schema emitter (tbd in W-15633198)
OasTypePartEmitter(shape, ordering, references = references)(OasLikeShapeEmitterContextAdapter(newCtx))
.emit(b)
val entries =
AvroShapeEmitter(shape, ordering)(AvroShapeEmitterContext(newCtx.eh, newCtx.renderConfig)).entries()
b.obj((traverse(entries, _)))
}
)
}
Expand Down
Loading