lundi 30 novembre 2020

How can I build a nested list using builder pattern?

I am trying to make a query builder for GraphQL syntax, by using a builder pattern. I already did it for the filtering part:

Example where I would get the title for all the events from "somewhere" with a price lower than 100.:

val query = Query.Builder()
                  .filter(PLACE,"Somewhere")
                  .filter(PRICELT, 100)
                  .build()

So for that part ^ it was easy, because it was not nested. and I just used a MutableMap<ENUM, Any> in kotlin so that I can pass strings and ints and longs and so on, and then I have a method that collects everything to a string.

{events(place:"Somewhere", priceLT:100){title}}

But now I want to programatically make a way to say what data I want to get back from the query. (NOTE: before the title part of the query was hardcoded into the QueryBuilder.

So for that I made another class called Entity which has a list of itself as an attribute.

class Entity(
        private val entity: EntityType,
        private val entities: MutableList<Entity> = mutableListOf()
) {
    override fun toString(): String {
        return if (this.entities.isEmpty()) {
            entity.str
        } else {
            this.entity.str + "{" + this.entities.onEach {
                it.toString()
            }.joinToString(" ") + "}"
        }
    }
}

And I got it to work. But to build the structure. then I have to append to the list and "visit" the list of the entity to visit the entities in that list.

For example if I want to build this query:

{
    events{
        title
        location {
            coordinates {
                longitude
                latiitude
            }
        }
    }
}

Then I need to go 2 levels down in the layers of the Entity, And the way I am doing it now, then my code is getting very wide.

fun main() {
    val e1 = Entity(
            EntityType.EVENTS,
            mutableListOf(
                    Entity(EntityType.TITLE),
                    Entity(EntityType.LOCATION,
                            mutableListOf(
                                    Entity(EntityType.COORDINATES,
                                            mutableListOf(
                                                    Entity(EntityType.LONGITUDE),
                                                    Entity(EntityType.LATITUDE)
                                            )
                                    )
                            )
                    ),

            )
    )
}

So I was thinking to use builder pattern. Or something else which will be smarter.

Thank you in advance for your help, and have a nice day.

BTW: I am using following enums:

enum class EntityType(val str: String) {
    EVENTS("events"),
    TITLE("title"),
    GENRE("genre"),
    IMAGE("image"),
    LINK("link"),
    OTHER("other"),
    PRICE("price"),
    TEXT("text"),
    TICKETS("tickets"),
    TIME("time"),
    LOCATION("location"),
    AREA("area"),
    PLACE("place"),
    ADDRESS("address"),
    CITY("city"),
    STREET("street"),
    NO("no"),
    STATE("state"),
    ZIP("zip"),
    COORDINATES("coordinates"),
    LONGITUDE("longitude"),
    LATITUDE("latitude"),
}

enum class Filter(val str: String) {
    PLACE("place"),
    PRICELT("priceLT"),
    PRICEGT("priceGT"),
    TIMELT("timestampLT"),
    TIMEGT("timestampGT"),
    AREA("area"),
    TITLE("title"),
    GENRE("genre")
}

And the rest of my code looks like this:

I share it for transparency, even though its not that relevant to my question. Later I will implement the Entities in the code where i have written the comments.

class Query private constructor(val query: String) {
    class Builder {
        private var query = ""
        private var filters = mutableMapOf<Filter, Any>()
        private var entities = Entity(EntityType.EVENTS) // I started merging the entities into the query

        fun filter(key: Filter, value: Any) = apply {
            this.filters[key] = value
        }

        fun build(): Query {
            this.query = "{events{" // Here the first part of the event string should come.
            if (filters.isNotEmpty()) {
                this.query += "("
                filters.forEach {
                    this.query += if (it.value is Int || it.value is Long) it.key.str + ":" + it.value + "," else {
                        it.key.str + ":\"" + it.value + "\","
                    }
                }
                this.query = this.query.dropLast(1) + ")"
            }
            this.query += "title}}" // Here the Entities.toString will be called
            return Query(this.query)
        }
    }

}

Aucun commentaire:

Enregistrer un commentaire