@@ -16,6 +16,8 @@ import shapeless.{:+:, CNil, Coproduct, Inl, Inr, Lazy}
1616import shapeless .ops .coproduct .{Inject , Selector }
1717import vulcan .internal .converters .collection ._
1818import vulcan .internal .tags ._
19+ import cats .data .Chain
20+ import cats .free .FreeApplicative
1921
2022package object generic {
2123 implicit final val cnilCodec : Codec .Aux [Nothing , CNil ] =
@@ -112,144 +114,52 @@ package object generic {
112114 implicit final class MagnoliaCodec private [generic] (
113115 private val codec : Codec .type
114116 ) extends AnyVal {
115- final def combine [A ](caseClass : CaseClass [Codec , A ]): Codec [A ] = {
116- val namespace =
117- caseClass.annotations
118- .collectFirst { case AvroNamespace (namespace) => namespace }
119- .getOrElse(caseClass.typeName.owner)
120-
121- val shortName =
122- caseClass.annotations
123- .collectFirst { case AvroName (namespace) => namespace }
124- .getOrElse(caseClass.typeName.short)
125-
126- val typeName =
127- s " $namespace. $shortName"
128-
129- val schema =
130- if (caseClass.isValueClass) {
131- caseClass.parameters.head.typeclass.schema
132- } else {
133- AvroError .catchNonFatal {
117+ final def combine [A ](caseClass : CaseClass [Codec , A ]): Codec [A ] =
118+ if (caseClass.isValueClass) {
119+ val param = caseClass.parameters.head
120+ param.typeclass.imap(value => caseClass.rawConstruct(List (value)))(param.dereference)
121+ } else {
122+
123+ Codec
124+ .record[A ](
125+ name = caseClass.annotations
126+ .collectFirst { case AvroName (namespace) => namespace }
127+ .getOrElse(caseClass.typeName.short),
128+ namespace = caseClass.annotations
129+ .collectFirst { case AvroNamespace (namespace) => namespace }
130+ .getOrElse(caseClass.typeName.owner),
131+ doc = caseClass.annotations.collectFirst {
132+ case AvroDoc (doc) => doc
133+ }
134+ ) { (f : Codec .FieldBuilder [A ]) =>
134135 val nullDefaultBase = caseClass.annotations
135136 .collectFirst { case AvroNullDefault (enabled) => enabled }
136137 .getOrElse(false )
137138
138- val fields =
139- caseClass.parameters.toList. traverse { param =>
140- param.typeclass.schema.map { schema =>
141- def nullDefaultField =
142- param.annotations
143- .collectFirst {
144- case AvroNullDefault (nullDefault) => nullDefault
145- }
146- .getOrElse(nullDefaultBase)
147-
148- new Schema . Field (
149- param.label,
150- schema ,
151- param.annotations.collectFirst {
152- case AvroDoc ( doc) => doc
153- }.orNull,
154- if (schema.isNullable && nullDefaultField) Schema . Field . NULL_DEFAULT_VALUE
155- else null
156- )
157- }
139+ caseClass.parameters.toList
140+ . traverse[ FreeApplicative [ Codec . Field [ A , * ], * ], Any ] { param =>
141+ def nullDefaultField =
142+ param.annotations
143+ .collectFirst {
144+ case AvroNullDefault (nullDefault) => nullDefault
145+ }
146+ .getOrElse(nullDefaultBase)
147+
148+ implicit val codec = param.typeclass
149+
150+ f(
151+ name = param.label ,
152+ access = param.dereference,
153+ doc = param.annotations.collectFirst {
154+ case AvroDoc (doc) => doc
155+ },
156+ default = ( if (codec.schema.exists(_.isNullable) && nullDefaultField) Some ( None )
157+ else None ). asInstanceOf [ Option [param. PType ]] // TODO: remove cast
158+ ).widen
158159 }
159-
160- fields.map { fields =>
161- Schema .createRecord(
162- caseClass.annotations
163- .collectFirst {
164- case AvroName (name) => name
165- }
166- .getOrElse(
167- caseClass.typeName.short
168- ),
169- caseClass.annotations.collectFirst {
170- case AvroDoc (doc) => doc
171- }.orNull,
172- namespace,
173- false ,
174- fields.asJava
175- )
176- }
160+ .map(caseClass.rawConstruct(_))
177161 }
178- }
179- Codec
180- .instance[Any , A ](
181- schema,
182- if (caseClass.isValueClass) { a =>
183- val param = caseClass.parameters.head
184- param.typeclass.encode(param.dereference(a))
185- } else
186- (a : A ) =>
187- schema.flatMap { schema =>
188- val fields =
189- caseClass.parameters.toList.traverse { param =>
190- param.typeclass
191- .encode(param.dereference(a))
192- .tupleLeft(param.label)
193- }
194-
195- fields.map { values =>
196- val record = new GenericData .Record (schema)
197- values.foreach {
198- case (label, value) =>
199- record.put(label, value)
200- }
201-
202- record
203- }
204- },
205- if (caseClass.isValueClass) { (value, schema) =>
206- caseClass.parameters.head.typeclass
207- .decode(value, schema)
208- .map(decoded => caseClass.rawConstruct(List (decoded)))
209- } else
210- (value, writerSchema) => {
211- writerSchema.getType() match {
212- case Schema .Type .RECORD =>
213- value match {
214- case record : IndexedRecord =>
215- caseClass.parameters.toList
216- .traverse {
217- param =>
218- val field = record.getSchema.getField(param.label)
219- if (field != null ) {
220- val value = record.get(field.pos)
221- param.typeclass.decode(value, field.schema())
222- } else {
223- schema.flatMap { readerSchema =>
224- readerSchema.getFields.asScala
225- .find(_.name == param.label)
226- .filter(_.hasDefaultValue)
227- .toRight(AvroError .decodeMissingRecordField(param.label))
228- .flatMap(
229- readerField => param.typeclass.decode(null , readerField.schema)
230- )
231- }
232- }
233- }
234- .map(caseClass.rawConstruct)
235-
236- case other =>
237- Left (AvroError .decodeUnexpectedType(other, " IndexedRecord" ))
238- }
239-
240- case schemaType =>
241- Left {
242- AvroError
243- .decodeUnexpectedSchemaType(
244- schemaType,
245- Schema .Type .RECORD
246- )
247- }
248- }
249- }
250- )
251- .withTypeName(typeName)
252- }
162+ }
253163
254164 /**
255165 * Returns a `Codec` instance for the specified type,
@@ -260,68 +170,15 @@ package object generic {
260170 macro Magnolia .gen[A ]
261171
262172 final def dispatch [A ](sealedTrait : SealedTrait [Codec , A ]): Codec .Aux [Any , A ] = {
263- val typeName = sealedTrait.typeName.full
264- Codec
265- .instance[Any , A ](
266- AvroError .catchNonFatal {
267- sealedTrait.subtypes.toList
268- .traverse(_.typeclass.schema)
269- .map(schemas => Schema .createUnion(schemas.asJava))
270- },
271- a =>
272- sealedTrait.dispatch(a) { subtype =>
273- subtype.typeclass.encode(subtype.cast(a))
274- },
275- (value, schema) => {
276- val schemaTypes =
277- schema.getType() match {
278- case Schema .Type .UNION => schema.getTypes.asScala
279- case _ => Seq (schema)
280- }
281-
282- value match {
283- case container : GenericContainer =>
284- val subtypeName =
285- container.getSchema.getName
286173
287- val subtypeUnionSchema =
288- schemaTypes
289- .find(_.getName == subtypeName)
290- .toRight(AvroError .decodeMissingUnionSchema(subtypeName))
291-
292- def subtypeMatching =
293- sealedTrait.subtypes
294- .find(_.typeclass.schema.exists(_.getName == subtypeName))
295- .toRight(AvroError .decodeMissingUnionAlternative(subtypeName))
296-
297- subtypeUnionSchema.flatMap { subtypeSchema =>
298- subtypeMatching.flatMap { subtype =>
299- subtype.typeclass.decode(container, subtypeSchema)
300- }
301- }
302-
303- case other =>
304- sealedTrait.subtypes.toList
305- .collectFirstSome { subtype =>
306- subtype.typeclass.schema
307- .traverse { subtypeSchema =>
308- val subtypeName = subtypeSchema.getName
309- schemaTypes
310- .find(_.getName == subtypeName)
311- .flatMap { schema =>
312- subtype.typeclass
313- .decode(other, schema)
314- .toOption
315- }
316- }
317- }
318- .getOrElse {
319- Left (AvroError .decodeExhaustedAlternatives(other))
320- }
174+ Codec
175+ .union[A ](
176+ alt =>
177+ Chain .fromSeq(sealedTrait.subtypes).flatMap { subtype =>
178+ alt(subtype.typeclass, Prism .instance(subtype.cast.lift)(identity))
321179 }
322- }
323180 )
324- .withTypeName(typeName)
181+ .withTypeName(sealedTrait. typeName.full )
325182 }
326183
327184 final type Typeclass [A ] = Codec [A ]
0 commit comments