commit 91e988f72217eefa1e82198b3b097b4130237650 Author: Hans Mündelein Date: Wed May 17 18:30:26 2023 +0200 Add initial draft for core First attempt for common types and the graphql schema diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3ddfe6e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["crabidy-core"] diff --git a/Draft.rnote b/Draft.rnote new file mode 100644 index 0000000..0d7c3ba Binary files /dev/null and b/Draft.rnote differ diff --git a/crabidy-core/Cargo.toml b/crabidy-core/Cargo.toml new file mode 100644 index 0000000..12d3a9c --- /dev/null +++ b/crabidy-core/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "crabidy-core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "0.1.68" +futures = "0.3.28" +graphql_client = "0.12.0" +juniper = { git = "https://github.com/graphql-rust/juniper.git", branch = "master" } +juniper_subscriptions = { git = "https://github.com/graphql-rust/juniper.git", branch = "master" } +serde = "1.0.163" +# juniper = { version = "0.16.0", features = ["serde_json"] } + +[build-dependencies] +async-trait = "0.1.68" +futures = "0.3.28" +graphql_client = "0.12.0" +juniper = { git = "https://github.com/graphql-rust/juniper.git", branch = "master" } +juniper_subscriptions = { git = "https://github.com/graphql-rust/juniper.git", branch = "master" } +serde = "1.0.163" +# juniper = { version = "0.16.0", features = ["serde_json"] } diff --git a/crabidy-core/build.rs b/crabidy-core/build.rs new file mode 100644 index 0000000..3f77842 --- /dev/null +++ b/crabidy-core/build.rs @@ -0,0 +1,32 @@ +use juniper::{EmptyMutation, EmptySubscription, IntrospectionFormat}; +use std::fs::File; +use std::io::prelude::*; + +#[path = "src/lib.rs"] +mod lib; + +fn main() { + // let (res, _errors) = juniper::introspect( + // &lib::Schema::new( + // lib::ItemList::new(), + // lib::Queue::new(), + // lib::Subscription::new(), + // ), + // &(), + // IntrospectionFormat::default(), + // ) + // .unwrap(); + // let mut file = File::create("src/schema.json").unwrap(); + // let json_result = serde_json::to_string_pretty(&res).unwrap(); + // file.write_all(json_result.as_bytes()).unwrap(); + + let schema = lib::Schema::new( + lib::ItemList::new(), + lib::Mutation::new(), + lib::Subscription::new(), + ); + let schema_str = schema.as_schema_language(); + + let mut file = File::create("src/schema.graphql").unwrap(); + file.write_all(schema_str.as_bytes()).unwrap(); +} diff --git a/crabidy-core/src/lib.rs b/crabidy-core/src/lib.rs new file mode 100644 index 0000000..e15967e --- /dev/null +++ b/crabidy-core/src/lib.rs @@ -0,0 +1,328 @@ +use async_trait::async_trait; +use futures::Stream; +use juniper::{ + graphql_object, graphql_subscription, FieldError, FieldResult, GraphQLEnum, GraphQLInputObject, + GraphQLObject, RootNode, +}; +use std::{collections::HashMap, pin::Pin, sync::Arc}; + +#[async_trait] +pub trait ProviderClient: Send + Sync { + async fn get_urls_for_track(&self, track_uuid: &str) -> Result, ProviderError>; + async fn get_item_lists_full(&self) -> Result, ProviderError>; + async fn get_item_lists_partial( + &self, + list_uuid: String, + ) -> Result, ProviderError>; + async fn get_item_list_partial_without_children( + &self, + list_uuid: String, + ) -> Result; +} + +#[derive(Clone, Debug, Hash)] +pub enum ProviderError { + UnknownUser, + FetchError, +} + +#[derive(Clone, Debug)] +pub struct ItemList { + pub name: String, + pub uuid: String, + pub provider: String, + pub tracks: Option>, + pub children: Option>, + pub is_queable: bool, + without_children: bool, +} + +#[graphql_object(context = Context)] +impl ItemList { + fn name(&self) -> &str { + &self.name + } + fn uuid(&self) -> &str { + &self.uuid + } + fn provider(&self) -> &str { + &self.provider + } + //TODO: Be smarter than clone here + fn tracks(&self, refresh: bool) -> Option> { + self.tracks.clone() + } + //TODO: Be smarter than clone here + fn children(&self, refresh: bool) -> Option> { + self.children.clone() + } + fn is_queable(&self) -> bool { + self.is_queable + } +} + +impl ItemList { + pub fn new() -> Self { + Self { + name: "root".to_string(), + uuid: "root".to_string(), + provider: "root".to_string(), + tracks: None, + children: None, + is_queable: false, + without_children: true, + } + } + pub fn flatten(&self) -> Vec { + let mut tracks = Vec::new(); + if let Some(own_tracks) = &self.tracks { + tracks.extend(own_tracks.clone()); + } + if let Some(childs) = &self.children { + for child in childs { + tracks.extend(child.flatten()); + } + } + tracks + } +} + +#[derive(Clone, Debug, GraphQLEnum)] +enum PlayState { + Buffering, + Playing, + Paused, + Stopped, +} + +#[derive(Clone, Debug, GraphQLObject)] +pub struct Track { + pub title: String, + pub uuid: String, + pub duration: Option, + pub album: Option, + pub artist: Option, + pub provider: String, +} + +#[derive(Clone, Debug, GraphQLInputObject)] +pub struct InputTrack { + pub uuid: String, + pub provider: String, +} + +#[derive(Clone, Debug, GraphQLObject)] +pub struct Album { + pub title: String, + pub release_date: Option, +} + +#[derive(Clone, Debug, GraphQLObject)] +pub struct Artist { + pub name: String, +} + +#[derive(Clone, Debug, Hash)] +pub enum QueueError { + ItemListNotQueuable, +} + +#[derive(Clone, Debug, GraphQLObject)] +pub struct Queue { + pub tracks: Vec, + current: i32, +} + +impl Queue { + pub fn new() -> Self { + Self { + tracks: vec![], + current: 0, + } + } + + pub fn clear(&mut self) { + self.current = 0; + self.tracks = vec![]; + } + + pub fn replace_with_track(&mut self, track: Track) { + self.current = 0; + self.tracks = vec![track]; + } + + pub fn replace_with_item_list(&mut self, item_list: &ItemList) -> Result<(), QueueError> { + if !item_list.is_queable { + return Err(QueueError::ItemListNotQueuable); + }; + self.current = 0; + self.tracks = item_list.flatten(); + Ok(()) + } + + pub fn set_current(&mut self, current: usize) -> bool { + if current < self.tracks.len() { + self.current = current as i32; + true + } else { + false + } + } + + pub fn current(&self) -> Option<&Track> { + self.tracks.get(self.current as usize) + } + + pub fn next(&mut self) -> Option<&Track> { + if (self.current as usize) < self.tracks.len() { + self.current += 1; + Some(&self.tracks[(self.current - 1) as usize]) + } else { + None + } + } + + pub fn previous(&mut self) -> Option<&Track> { + if self.current > 0 { + self.current -= 1; + Some(&self.tracks[self.current as usize]) + } else { + None + } + } + + pub fn append_track(&mut self, track: Track) { + self.tracks.push(track); + } + + pub fn append_playlist(&mut self, playlist: &[Track]) { + self.tracks.extend(playlist.to_vec()); + } + + pub fn queue_track(&mut self, track: Track) { + self.tracks.insert(self.current as usize, track); + } + + pub fn queue_playlist(&mut self, playlist: &[Track]) { + let tail: Vec = self + .tracks + .splice((self.current as usize).., playlist.to_vec()) + .collect(); + self.tracks.extend(tail); + } +} + +#[derive(Clone, Debug)] +pub struct Mutation; + +impl Mutation { + pub fn new() -> Self { + Self + } +} + +#[graphql_object(context = Context)] +impl Mutation { + fn playpause(&self, track: InputTrack) -> FieldResult { + Ok(ActiveTrack::new()) + } + fn stop(&self, track: InputTrack) -> FieldResult { + Ok(ActiveTrack::new()) + } + fn previous(&self, track: InputTrack) -> FieldResult { + Ok(ActiveTrack::new()) + } + fn next(&self, track: InputTrack) -> FieldResult { + Ok(ActiveTrack::new()) + } + fn seek(&self, track: InputTrack, millis: i32) -> FieldResult { + Ok(ActiveTrack::new()) + } + fn append(&self, tracks: Vec) -> FieldResult { + Ok(Success::Appending) + } + fn queue(&self, tracks: Vec) -> FieldResult { + Ok(Success::Queuing) + } + fn replace(&self, tracks: Vec) -> FieldResult { + Ok(Success::Replacing) + } + fn delete(&self, track: InputTrack) -> FieldResult { + Ok(Success::Deleting) + } + fn clear(&self, track: InputTrack) -> FieldResult { + Ok(Success::Clearing) + } +} + +#[derive(Clone, Debug, GraphQLEnum)] +enum Success { + Appending, + Replacing, + Queuing, + Deleting, + Clearing, +} + +#[derive(Clone, Debug, GraphQLObject)] +pub struct ActiveTrack { + track: Option, + completion: i32, + play_state: PlayState, +} + +impl ActiveTrack { + pub fn new() -> Self { + Self { + track: None, + completion: 0, + play_state: PlayState::Stopped, + } + } +} + +type ActiveTrackStream = Pin> + Send>>; +type QueueStream = Pin> + Send>>; + +#[derive(Clone, Debug)] +pub struct Subscription { + queue: Queue, + active_track: Option, +} + +#[graphql_subscription(context = Context)] +impl Subscription { + async fn queue() -> QueueStream { + let stream = futures::stream::iter(vec![Ok(Queue::new())]); + Box::pin(stream) + } + async fn active_track() -> ActiveTrackStream { + let stream = futures::stream::iter(vec![Ok(ActiveTrack::new())]); + Box::pin(stream) + } +} + +impl Subscription { + pub fn new() -> Self { + Self { + queue: Queue::new(), + active_track: None, + } + } +} + +pub struct Context { + clients: HashMap>, + queue: Queue, +} + +impl Context { + pub fn new(clients: HashMap>) -> Self { + let queue = Queue::new(); + Self { clients, queue } + } +} + +impl juniper::Context for Context {} + +pub type Schema = RootNode<'static, ItemList, Mutation, Subscription>; diff --git a/crabidy-core/src/query.graphql b/crabidy-core/src/query.graphql new file mode 100644 index 0000000..748d839 --- /dev/null +++ b/crabidy-core/src/query.graphql @@ -0,0 +1,3 @@ +query ItemList { + name +} diff --git a/crabidy-core/src/query.rs b/crabidy-core/src/query.rs new file mode 100644 index 0000000..390fcb3 --- /dev/null +++ b/crabidy-core/src/query.rs @@ -0,0 +1,35 @@ +#![allow(clippy::all, warnings)] +pub struct ItemList; +pub mod item_list { + #![allow(dead_code)] + use std::result::Result; + pub const OPERATION_NAME: &str = "ItemList"; + pub const QUERY: &str = "query ItemList {\n name\n}\n"; + use super::*; + use serde::{Deserialize, Serialize}; + #[allow(dead_code)] + type Boolean = bool; + #[allow(dead_code)] + type Float = f64; + #[allow(dead_code)] + type Int = i64; + #[allow(dead_code)] + type ID = String; + #[derive(Serialize)] + pub struct Variables; + #[derive(Deserialize)] + pub struct ResponseData { + pub name: String, + } +} +impl graphql_client::GraphQLQuery for ItemList { + type Variables = item_list::Variables; + type ResponseData = item_list::ResponseData; + fn build_query(variables: Self::Variables) -> ::graphql_client::QueryBody { + graphql_client::QueryBody { + variables, + query: item_list::QUERY, + operation_name: item_list::OPERATION_NAME, + } + } +} diff --git a/crabidy-core/src/schema.graphql b/crabidy-core/src/schema.graphql new file mode 100644 index 0000000..ed3faae --- /dev/null +++ b/crabidy-core/src/schema.graphql @@ -0,0 +1,81 @@ +type Queue { + tracks: [Track!]! + current: Int! +} + +type Mutation { + playpause(track: InputTrack!): ActiveTrack! + stop(track: InputTrack!): ActiveTrack! + previous(track: InputTrack!): ActiveTrack! + next(track: InputTrack!): ActiveTrack! + seek(track: InputTrack!, millis: Int!): ActiveTrack! + append(tracks: [InputTrack!]!): Success! + queue(tracks: [InputTrack!]!): Success! + replace(tracks: [InputTrack!]!): Success! + delete(track: InputTrack!): Success! + clear(track: InputTrack!): Success! +} + +type Album { + title: String! + releaseDate: String +} + +enum PlayState { + BUFFERING + PLAYING + PAUSED + STOPPED +} + +type Subscription { + queue: Queue! + activeTrack: ActiveTrack! +} + +type Artist { + name: String! +} + +type ActiveTrack { + track: Track + completion: Int! + playState: PlayState! +} + +type ItemList { + name: String! + uuid: String! + provider: String! + tracks(refresh: Boolean!): [Track!] + children(refresh: Boolean!): [ItemList!] + isQueable: Boolean! +} + +type Track { + title: String! + uuid: String! + duration: Int + album: Album + artist: Artist + provider: String! +} + +enum Success { + APPENDING + REPLACING + QUEUING + DELETING + CLEARING +} + +input InputTrack { + uuid: String! + provider: String! +} + +schema { + query: ItemList + mutation: Mutation + subscription: Subscription +} diff --git a/crabidy-core/src/schema.json b/crabidy-core/src/schema.json new file mode 100644 index 0000000..7c35a90 --- /dev/null +++ b/crabidy-core/src/schema.json @@ -0,0 +1,1386 @@ +{ + "__schema": { + "description": null, + "queryType": { + "name": "ItemList" + }, + "mutationType": { + "name": "Queue" + }, + "subscriptionType": { + "name": "Subscription" + }, + "types": [ + { + "kind": "SCALAR", + "name": "Boolean", + "description": null, + "specifiedByUrl": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": null, + "specifiedByUrl": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Queue", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "tracks", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Track", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "current", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Album", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "title", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "releaseDate", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "GraphQL type kind\n\nThe GraphQL specification defines a number of type kinds - the meta type of a type.", + "specifiedByUrl": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "## Scalar types\n\nScalar types appear as the leaf nodes of GraphQL queries. Strings, numbers, and booleans are the built in types, and while it's possible to define your own, it's relatively uncommon.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "## Object types\n\nThe most common type to be implemented by users. Objects have fields and can implement interfaces.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "## Interface types\n\nInterface types are used to represent overlapping fields between multiple types, and can be queried for their concrete type.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "## Union types\n\nUnions are similar to interfaces but can not contain any fields on their own.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "## Enum types\n\nLike scalars, enum types appear as the leaf nodes of GraphQL queries.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "## Input objects\n\nRepresents complex values provided in queries _into_ the system.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "## List types\n\nRepresent lists of other types. This library provides implementations for vectors and slices, but other Rust types can be extended to serve as GraphQL lists.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "## Non-null types\n\nIn GraphQL, nullable types are the default. By putting a `!` after a type, it becomes non-nullable.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "specifiedByUrl", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Subscription", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "queue", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Queue", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "types", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": null, + "specifiedByUrl": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Artist", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ItemList", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uuid", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "provider", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tracks", + "description": null, + "args": [ + { + "name": "refresh", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Track", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "children", + "description": null, + "args": [ + { + "name": "refresh", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ItemList", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isQueable", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": null, + "specifiedByUrl": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIABLE_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Track", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "title", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uuid", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "duration", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "album", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "Album", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "artist", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "Artist", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "provider", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": null, + "specifiedByUrl": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isRepeatable", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use the locations array instead" + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use the locations array instead" + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use the locations array instead" + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": null, + "isRepeatable": false, + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": null, + "isRepeatable": false, + "locations": [ + "FIELD_DEFINITION", + "ENUM_VALUE" + ], + "args": [ + { + "name": "reason", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "specifiedBy", + "description": null, + "isRepeatable": false, + "locations": [ + "SCALAR" + ], + "args": [ + { + "name": "url", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": null, + "isRepeatable": false, + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + } + ] + } +} \ No newline at end of file