All Downloads are FREE. Search and download functionalities are using the official Maven repository.

smithy4s.json.internals.Cursor.scala Maven / Gradle / Ivy

There is a newer version: 0.19.0-41-91762fb
Show newest version
/*
 *  Copyright 2021-2024 Disney Streaming
 *
 *  Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     https://disneystreaming.github.io/TOST-1.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package smithy4s
package json
package internals

import com.github.plokhotnyuk.jsoniter_scala.core.JsonReader
import com.github.plokhotnyuk.jsoniter_scala.core.JsonReaderException
import smithy4s.codecs._

private[internals] class Cursor private[internals] () {
  private[this] var indexStack: Array[Int] = new Array[Int](8)
  private[this] var labelStack: Array[String] = new Array[String](8)
  private[this] var top: Int = _
  private var expecting: String = _

  def decode[A](codec: JCodec[A], in: JsonReader): A = {
    this.expecting = codec.expecting
    codec.decodeValue(this, in)
  }

  def under[A](segment: PayloadPath.Segment)(f: => A): A =
    segment match {
      case i: PayloadPath.Segment.Index => under(i.index)(f)
      case l: PayloadPath.Segment.Label => under(l.label)(f)
    }

  def under[A](label: String)(f: => A): A = {
    push(label)
    val res = f
    pop()
    res
  }

  def under[A](index: Int)(f: => A): A = {
    push(index)
    val res = f
    pop()
    res
  }

  def push(label: String): Unit = {
    if (top >= labelStack.length) growStacks()
    labelStack(top) = label
    top += 1
  }

  def push(index: Int): Unit = {
    if (top >= indexStack.length) growStacks()
    indexStack(top) = index
    top += 1
  }

  def pop(): Unit = {
    if (top > 0) {
      top -= 1
    }
    labelStack(top) = null
  }

  def payloadError[A](codec: JCodec[A], message: String): Nothing =
    throw new PayloadError(getPath(Nil), codec.expecting, message)

  def requiredFieldError[A](codec: JCodec[A], field: String): Nothing =
    requiredFieldError(codec.expecting, field)

  def requiredFieldError[A](expecting: String, field: String): Nothing = {
    val path = getPath(new PayloadPath.Segment.Label(field) :: Nil)
    throw new PayloadError(path, expecting, "Missing required field")
  }

  private[internals] def getPath(
      segments: List[PayloadPath.Segment]
  ): PayloadPath = {
    var top = this.top
    var list = segments
    while (top > 0) {
      top -= 1
      val label = labelStack(top)
      val segment =
        if (label ne null) new PayloadPath.Segment.Label(label)
        else new PayloadPath.Segment.Index(indexStack(top))
      list = segment :: list
    }
    new PayloadPath(list)
  }

  private def getExpected(): String =
    if (expecting != null) expecting
    else throw new IllegalStateException("Expected should have been fulfilled")

  private[this] def growStacks(): Unit = {
    val size = top << 1
    labelStack = java.util.Arrays.copyOf(labelStack, size)
    indexStack = java.util.Arrays.copyOf(indexStack, size)
  }
}

object Cursor {

  def withCursor[A](expecting: String)(f: Cursor => A): A = {
    val cursor = new Cursor()
    cursor.expecting = expecting
    try f(cursor)
    catch {
      case e: JsonReaderException => payloadError(cursor, e.getMessage())
      case e: ConstraintError     => payloadError(cursor, e.message)
    }
  }

  private[this] def payloadError(cursor: Cursor, message: String): Nothing =
    throw new PayloadError(cursor.getPath(Nil), cursor.getExpected(), message)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy