463 lines
12 KiB
Ruby
463 lines
12 KiB
Ruby
# The file was generated in several steps.
|
|
#
|
|
# 1. Convert airbyte_protocol.yaml to JSON
|
|
# 2. Use https://app.quicktype.io/?l=ruby to generate code. Parameters:
|
|
# Type strictness: Coercible
|
|
# Plain types only: Disabled
|
|
# 3. Fix module `Types` according to https://dry-rb.org/gems/dry-types/master/built-in-types/
|
|
# 4. Replace all instance variable calls to just method calls (remove all characters)
|
|
# 4. Add `.compact` call to resulting object in every `to_dynamic` method
|
|
#
|
|
#
|
|
#
|
|
# This code may look unusually verbose for Ruby (and it is), but
|
|
# it performs some subtle and complex validation of JSON data.
|
|
#
|
|
# To parse this JSON, add 'dry-struct' and 'dry-types' gems, then do:
|
|
#
|
|
# airbyte = Airbyte.from_json! "{…}"
|
|
# puts airbyte.configured_airbyte_catalog&.streams.first.stream.supported_sync_modes&.first
|
|
#
|
|
# If from_json! succeeds, the value returned matches the schema.
|
|
|
|
require 'json'
|
|
require 'dry-types'
|
|
require 'dry-struct'
|
|
|
|
module Types
|
|
include Dry::Types()
|
|
|
|
Int = Coercible::Integer
|
|
Bool = Strict::Bool
|
|
Hash = Coercible::Hash
|
|
String = Coercible::String
|
|
Type = Coercible::String.enum("CATALOG", "CONNECTION_STATUS", "LOG", "RECORD", "SPEC", "STATE")
|
|
SyncMode = Coercible::String.enum("full_refresh", "incremental")
|
|
Status = Coercible::String.enum("FAILED", "SUCCEEDED")
|
|
Level = Coercible::String.enum("DEBUG", "ERROR", "FATAL", "INFO", "TRACE", "WARN")
|
|
end
|
|
|
|
# Message type
|
|
module Type
|
|
Catalog = "CATALOG"
|
|
ConnectionStatus = "CONNECTION_STATUS"
|
|
Log = "LOG"
|
|
Record = "RECORD"
|
|
Spec = "SPEC"
|
|
State = "STATE"
|
|
end
|
|
|
|
module SyncMode
|
|
FullRefresh = "full_refresh"
|
|
Incremental = "incremental"
|
|
end
|
|
|
|
class AirbyteStream < Dry::Struct
|
|
|
|
# Path to the field that will be used to determine if a record is new or modified since the
|
|
# last sync. If not provided by the source, the end user will have to specify the
|
|
# comparable themselves.
|
|
attribute :default_cursor_field, Types.Array(Types::String).optional
|
|
|
|
# Stream schema using Json Schema specs.
|
|
attribute :json_schema, Types::Hash.meta(of: Types::Any)
|
|
|
|
# Stream's name.
|
|
attribute :airbyte_stream_name, Types::String
|
|
|
|
# If the source defines the cursor field, then it does any other cursor field inputs will
|
|
# be ignored. If it does not either the user_provided one is used or as a backup the
|
|
# default one is used.
|
|
attribute :source_defined_cursor, Types::Bool.optional
|
|
|
|
attribute :supported_sync_modes, Types.Array(Types::SyncMode).optional
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
default_cursor_field: d["default_cursor_field"],
|
|
json_schema: Types::Hash[d.fetch("json_schema")].map { |k, v| [k, Types::Any[v]] }.to_h,
|
|
airbyte_stream_name: d.fetch("name"),
|
|
source_defined_cursor: d["source_defined_cursor"],
|
|
supported_sync_modes: d["supported_sync_modes"],
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"default_cursor_field" => default_cursor_field,
|
|
"json_schema" => json_schema,
|
|
"name" => airbyte_stream_name,
|
|
"source_defined_cursor" => source_defined_cursor,
|
|
"supported_sync_modes" => supported_sync_modes,
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
# log message: any kind of logging you want the platform to know about.
|
|
#
|
|
# Airbyte stream schema catalog
|
|
class AirbyteCatalog < Dry::Struct
|
|
attribute :streams, Types.Array(AirbyteStream)
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
streams: d.fetch("streams").map { |x| AirbyteStream.from_dynamic!(x) },
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"streams" => streams.map { |x| x.to_dynamic },
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
module Status
|
|
Failed = "FAILED"
|
|
Succeeded = "SUCCEEDED"
|
|
end
|
|
|
|
# Airbyte connection status
|
|
class AirbyteConnectionStatus < Dry::Struct
|
|
attribute :message, Types::String.optional
|
|
attribute :status, Types::Status
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
message: d["message"],
|
|
status: d.fetch("status"),
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"message" => message,
|
|
"status" => status,
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
# the type of logging
|
|
module Level
|
|
Debug = "DEBUG"
|
|
Error = "ERROR"
|
|
Fatal = "FATAL"
|
|
Info = "INFO"
|
|
Trace = "TRACE"
|
|
Warn = "WARN"
|
|
end
|
|
|
|
# log message: any kind of logging you want the platform to know about.
|
|
class AirbyteLogMessage < Dry::Struct
|
|
|
|
# the type of logging
|
|
attribute :level, Types::Level
|
|
|
|
# the log message
|
|
attribute :message, Types::String
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
level: d.fetch("level"),
|
|
message: d.fetch("message"),
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"level" => level,
|
|
"message" => message,
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
# record message: the record
|
|
class AirbyteRecordMessage < Dry::Struct
|
|
|
|
# the record data
|
|
attribute :data, Types::Hash.meta(of: Types::Any)
|
|
|
|
# when the data was emitted from the source. epoch in millisecond.
|
|
attribute :emitted_at, Types::Int
|
|
|
|
# the name of the stream for this record
|
|
attribute :stream, Types::String
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
data: Types::Hash[d.fetch("data")].map { |k, v| [k, Types::Any[v]] }.to_h,
|
|
emitted_at: d.fetch("emitted_at"),
|
|
stream: d.fetch("stream"),
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"data" => data,
|
|
"emitted_at" => emitted_at,
|
|
"stream" => stream,
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
# Specification of a connector (source/destination)
|
|
class ConnectorSpecification < Dry::Struct
|
|
attribute :changelog_url, Types::String.optional
|
|
|
|
# ConnectorDefinition specific blob. Must be a valid JSON string.
|
|
attribute :connection_specification, Types::Hash.meta(of: Types::Any)
|
|
|
|
attribute :documentation_url, Types::String.optional
|
|
|
|
# If the connector supports incremental mode or not.
|
|
attribute :supports_incremental, Types::Bool.optional
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
changelog_url: d["changelogUrl"],
|
|
connection_specification: Types::Hash[d.fetch("connectionSpecification")].map { |k, v| [k, Types::Any[v]] }.to_h,
|
|
documentation_url: d["documentationUrl"],
|
|
supports_incremental: d["supportsIncremental"],
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"changelogUrl" => changelog_url,
|
|
"connectionSpecification" => connection_specification,
|
|
"documentationUrl" => documentation_url,
|
|
"supportsIncremental" => supports_incremental,
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
# schema message: the state. Must be the last message produced. The platform uses this
|
|
# information
|
|
class AirbyteStateMessage < Dry::Struct
|
|
|
|
# the state data
|
|
attribute :data, Types::Hash.meta(of: Types::Any)
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
data: Types::Hash[d.fetch("data")].map { |k, v| [k, Types::Any[v]] }.to_h,
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"data" => data,
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
class AirbyteMessage < Dry::Struct
|
|
|
|
# log message: any kind of logging you want the platform to know about.
|
|
attribute :catalog, AirbyteCatalog.optional
|
|
|
|
attribute :connection_status, AirbyteConnectionStatus.optional
|
|
|
|
# log message: any kind of logging you want the platform to know about.
|
|
attribute :log, AirbyteLogMessage.optional
|
|
|
|
# record message: the record
|
|
attribute :record, AirbyteRecordMessage.optional
|
|
|
|
attribute :spec, ConnectorSpecification.optional
|
|
|
|
# schema message: the state. Must be the last message produced. The platform uses this
|
|
# information
|
|
attribute :state, AirbyteStateMessage.optional
|
|
|
|
# Message type
|
|
attribute :airbyte_message_type, Types::Type
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
catalog: d["catalog"] ? AirbyteCatalog.from_dynamic!(d["catalog"]) : nil,
|
|
connection_status: d["connectionStatus"] ? AirbyteConnectionStatus.from_dynamic!(d["connectionStatus"]) : nil,
|
|
log: d["log"] ? AirbyteLogMessage.from_dynamic!(d["log"]) : nil,
|
|
record: d["record"] ? AirbyteRecordMessage.from_dynamic!(d["record"]) : nil,
|
|
spec: d["spec"] ? ConnectorSpecification.from_dynamic!(d["spec"]) : nil,
|
|
state: d["state"] ? AirbyteStateMessage.from_dynamic!(d["state"]) : nil,
|
|
airbyte_message_type: d.fetch("type"),
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"catalog" => catalog&.to_dynamic,
|
|
"connectionStatus" => connection_status&.to_dynamic,
|
|
"log" => log&.to_dynamic,
|
|
"record" => record&.to_dynamic,
|
|
"spec" => spec&.to_dynamic,
|
|
"state" => state&.to_dynamic,
|
|
"type" => airbyte_message_type,
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
class ConfiguredAirbyteStream < Dry::Struct
|
|
|
|
# Path to the field that will be used to determine if a record is new or modified since the
|
|
# last sync. This field is REQUIRED if `sync_mode` is `incremental`. Otherwise it is
|
|
# ignored.
|
|
attribute :cursor_field, Types.Array(Types::String).optional
|
|
|
|
attribute :stream, AirbyteStream
|
|
attribute :sync_mode, Types::SyncMode.optional
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
cursor_field: d["cursor_field"],
|
|
stream: AirbyteStream.from_dynamic!(d.fetch("stream")),
|
|
sync_mode: d["sync_mode"],
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"cursor_field" => cursor_field,
|
|
"stream" => stream.to_dynamic,
|
|
"sync_mode" => sync_mode,
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
# Airbyte stream schema catalog
|
|
class ConfiguredAirbyteCatalog < Dry::Struct
|
|
attribute :streams, Types.Array(ConfiguredAirbyteStream)
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
streams: d.fetch("streams").map { |x| ConfiguredAirbyteStream.from_dynamic!(x) },
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"streams" => streams.map { |x| x.to_dynamic },
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|
|
|
|
# AirbyteProtocol structs
|
|
class Airbyte < Dry::Struct
|
|
attribute :airbyte_message, AirbyteMessage.optional
|
|
attribute :configured_airbyte_catalog, ConfiguredAirbyteCatalog.optional
|
|
|
|
def self.from_dynamic!(d)
|
|
d = Types::Hash[d]
|
|
new(
|
|
airbyte_message: d["airbyte_message"] ? AirbyteMessage.from_dynamic!(d["airbyte_message"]) : nil,
|
|
configured_airbyte_catalog: d["configured_airbyte_catalog"] ? ConfiguredAirbyteCatalog.from_dynamic!(d["configured_airbyte_catalog"]) : nil,
|
|
)
|
|
end
|
|
|
|
def self.from_json!(json)
|
|
from_dynamic!(JSON.parse(json))
|
|
end
|
|
|
|
def to_dynamic
|
|
{
|
|
"airbyte_message" => airbyte_message&.to_dynamic,
|
|
"configured_airbyte_catalog" => configured_airbyte_catalog&.to_dynamic,
|
|
}.compact
|
|
end
|
|
|
|
def to_json(options = nil)
|
|
JSON.generate(to_dynamic, options)
|
|
end
|
|
end
|