bliako has asked for the wisdom of the Perl Monks concerning the following question:
Esteemed Monks,
I am trying to create a Perl client for OpenTripPlanner (OTP). OTP can plan a public-transport trip (buses, trains, bikes, etc.). It is a real gem in the FOSS family. (For those interested this tutorial will setup a local server in 10 minutes: https://docs.opentripplanner.org/en/latest/Basic-Tutorial/. Including data and an underlying (OpenStreet)map layer for visualising the trips. Btw the data that feeds the beast - schedules etc. - is mostly public domain, in GTFS format, offered freely by transport agencies around the world and so is the optional underlying map (Open Street Map, another gem in FOSS).
An OTP server accepts queries in GraphQL and returns results back in JSON (I am very new to this GraphQL, so take these introductory with a pinch of salt). To get me started I have handcrafted some Perl classes corresponding to returned GraphQL objects (which in the returned JSON are hashtables).
After the investigation period, I am now retrieving the whole OTP GraphQL schema (see this SO answer for how but mostly this: https://medium.com/@mrthankyou/how-to-get-a-graphql-schema-28915025de0e) as JSON and I need to convert this schema to Perl Classes which each can take a JSON response and populate their corresponding object with that.
I have discovered a Perl module, GraphQL by ED J/etj++, which should be able to do what I need but it is unclear to me as to the how. Ideally I would like to feed it with the OTP GraphQL schema and let it create and write all the Perl Classes. Is this possible and how?
My OTP GraphQL schema is huge so I am not posting it here, but here is a CLI one-liner to retrieve one from local OTP server, if you have setup one -- or I can post my schema (350Kb) to my scratchpad and erase it in due time, let me know:"
curl \ --request POST --url 'http://localhost:8080/otp/gtfs/v1' \ --header 'Content-Type: application/json' \ --header 'OTPTimeout: 180000' \ --data '{"query":"query IntrospectionQuery { __schema { queryType +{ name } mutationType { name } subscriptionType { name } types { ...F +ullType } directives { name description locations args { ...InputValu +e } } } } fragment FullType on __Type { kind name description fields( +includeDeprecated: true) { name description args { ...InputValue } ty +pe { ...TypeRef } isDeprecated deprecationReason } inputFields { ...I +nputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: t +rue) { name description isDeprecated deprecationReason } possibleType +s { ...TypeRef } } fragment InputValue on __InputValue { name descrip +tion type { ...TypeRef } defaultValue } fragment TypeRef on __Type { +kind name ofType { kind name ofType { kind name ofType { kind name of +Type { kind name ofType { kind name ofType { kind name ofType { kind +name } } } } } } } } "}'
Update:
I have managed to convert the JSON schema into SDL (using graphql-introspection-json-to-sdl schema.json > schema.sdl which is a JS cli script installed with npm). Then I tried:
use GraphQL::Schema; my $FH; open($FH, '<:utf8', 'schema.sdl') or die "failed to open SDL file, $!" +; my $SDLcontent; { local $/ = undef; $SDLcontent = <$FH>; } close $FH; my $schema = GraphQL::Schema->from_doc($SDLcontent); if( ! defined $schema ){ die "failed to parse SDL schema." }
alas no joy because thunking failed:
Reference {"groupSimilarityKeepOne" => {"default_value" => "0.85","des +...} did not pass type constraint "CodeLike|FieldMapInput" (in $self- +>{"fields"}) at MooX/ Thunking.pm line 43 "CodeLike|FieldMapInput" requires that the value pass "CodeLike" o +r "FieldMapInput" Reference {"groupSimilarityKeepOne" => {"default_value" => "0.85", +"des...} did not pass type constraint "CodeLike" Reference {"groupSimilarityKeepOne" => {"default_value" => "0. +85","des...} did not pass type constraint "CodeLike" "CodeLike" is defined as: do { package Type::Tiny; ref($_) eq +q[CODE] or Scalar::Util::blessed($_) && (sub { require overload; over +load::Overloaded(ref $_[0] or $_[0]) and overload::Method((ref $_[0] +or $_[0]), $_[1]) })->($_, q[&{}]) } Reference {"groupSimilarityKeepOne" => {"default_value" => "0.85", +"des...} did not pass type constraint "FieldMapInput" "FieldMapInput" is a subtype of "Map[StrNameValid,Dict[default +_value=>Optional[Any],description=>Optional[Str],directives=>Optional +[ArrayRef[HashRef]],type=>ConsumerOf["GraphQL::Role::Input"]]]&__ANON +__" "Map[StrNameValid,Dict[default_value=>Optional[Any],descriptio +n=>Optional[Str],directives=>Optional[ArrayRef[HashRef]],type=>Consum +erOf["GraphQL::Role::Input"]]]&__ANON__" requires that the value pass + "Map[StrNameValid,Dict[default_value=>Optional[Any],description=>Opt +ional[Str],directives=>Optional[ArrayRef[HashRef]],type=>ConsumerOf[" +GraphQL::Role::Input"]]]" and "__ANON__" Reference {"groupSimilarityKeepOne" => {"default_value" => "0. +85","des...} did not pass type constraint "__ANON__" is defined as: ((do { package Type::Tiny; (ref($_) +eq 'HASH') and do { my $ok = 1; for my $i (values %{$_}) { ($ok = 0, +last) unless do { package Type::Tiny; (ref($i) eq 'HASH') and exists( +$i->{"type"}) and (do { use Scalar::Util (); Scalar::Util::blessed($i +->{"type"}) and do { my $method = $i->{"type"}->can('DOES')||$i->{"ty +pe"}->can('isa'); $i->{"type"}->$method(q[GraphQL::Role::Input]) } }) + } }; $ok } }) && do { package Type::Tiny; !grep { $_->{default_value} and !$_->{type}->is_valid($_->{default_v +alue}) } values %{$_} })
The offending content is:
""" Settings that control the behavior of itinerary filtering. **These are + advanced settings and should not be set by a user through user preferences.** """ input PlanItineraryFilterInput { """ Pick one itinerary from each group after putting itineraries that ar +e `85%` similar together, if the given value is `0.85`, for example. Itineraries are grouped t +ogether based on relative the distance of transit travel that is identical between the itinera +ries (access, egress and transfers are ignored). The value must be at least `0.5`. """ groupSimilarityKeepOne: Ratio = 0.85 """ Pick three itineraries from each group after putting itineraries tha +t are `68%` similar together, if the given value is `0.68`, for example. Itineraries are grouped t +ogether based on relative the distance of transit travel that is identical between the itinera +ries (access, egress and transfers are ignored). The value must be at least `0.5`. """ groupSimilarityKeepThree: Ratio = 0.68 """ Of the itineraries grouped to maximum of three itineraries, how much + worse can the non-grouped legs be compared to the lowest cost. `2.0` means that they can be do +uble the cost, and any itineraries having a higher cost will be filtered away. Use a value +lower than `1.0` to turn the grouping off. """ groupedOtherThanSameLegsMaxCostMultiplier: Float = 2 """ Itinerary filter debug profile used to control the behaviour of itin +erary filters. """ itineraryFilterDebugProfile: ItineraryFilterDebugProfile = OFF }
Thunked indeed
p.s. I put the extra-wide code into readmore tags hoping it will be more readable
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Converting a GraphQL schema into Perl classes
by etj (Priest) on Aug 30, 2024 at 15:48 UTC | |
by bliako (Abbot) on Aug 30, 2024 at 18:24 UTC |