Decode the protobuf wire format in Gleam! protobin.hexdocs.pm/
gleam protobuf
3

Configure Feed

Select the types of activity you want to include in your feed.

gtfs tests

Gavin Morrow (Sep 28, 2025, 9:04 PM EDT) dec19769 191e768f

+117 -22
test/gtfs-short.pb

This is a binary file and will not be displayed.

+37
test/gtfs-short.protoscope.txt
··· 1 + 1: {"1.0"} 2 + 3: 1758224061i64 3 + 1001: { 4 + 1: {"1.0"} 5 + 2: { 6 + 1: {"1"} 7 + 2: {2: 1758224061i64} 8 + } 9 + 2: { 10 + 1: {"2"} 11 + 2: {2: 1758224061i64} 12 + } 13 + # 2: { 14 + # 1: {6:SGROUP} 15 + # 2: {2: 1758224061i64} 16 + # } 17 + # 2: { 18 + # 1: {6:EGROUP} 19 + # 2: {2: 1758224061i64} 20 + # } 21 + 2: { 22 + 1: {"5"} 23 + 2: {2: 1758224061i64} 24 + } 25 + 2: { 26 + 1: {"6"} 27 + 2: {2: 1758224061i64} 28 + } 29 + 2: { 30 + 1: {"7"} 31 + 2: {2: 1758224061i64} 32 + } 33 + # 2: { 34 + # 1: {10:SGROUP} 35 + # 2: {2: 1758224061i64} 36 + # } 37 + }
+80 -22
test/protobuf_decode_gleam_test.gleam
··· 40 40 ) 41 41 use day <- decode.field(5, decoders.fixed(32)) 42 42 43 - decode.success(Person(id:, age:, score:, self: person, day:)) 43 + Person(id:, age:, score:, self: person, day:) |> decode.success 44 44 } 45 45 46 46 pub fn person_pb_test() { ··· 74 74 use id <- decode.field(1, decoders.uint()) 75 75 use age <- decode.field(2, decoders.uint()) 76 76 77 - decode.success(Test(id:, age:)) 77 + Test(id:, age:) |> decode.success 78 78 } 79 79 80 80 pub fn two_ints_test() { ··· 95 95 assert data == Test(id: 150, age: 80_150) 96 96 } 97 97 98 - type Gtfs { 99 - Gtfs(header: FeedHeader) 98 + pub type FeedHeader { 99 + FeedHeader(gtfs_realtime_version: String, timestamp: Int, nyct: NyctHeader) 100 100 } 101 101 102 - fn gtfs_decoder() -> Decoder(Gtfs) { 103 - use header <- decode.field( 104 - 1, 105 - decoders.protobuf( 106 - using: feed_header_decoder, 107 - named: "FeedHeader", 108 - default: feed_header_default, 109 - ), 102 + pub type NyctHeader { 103 + NyctHeader( 104 + version: String, 105 + trip_replacement_periods: List(TripReplacementPeriod), 110 106 ) 111 - decode.success(Gtfs(header:)) 112 107 } 113 108 114 - type FeedHeader { 115 - FeedHeader(version: String) 109 + pub type TripReplacementPeriod { 110 + TripReplacementPeriod(route_id: String, replacement_period: Int) 116 111 } 117 112 118 - const feed_header_default = FeedHeader(version: "0.0") 113 + const nyct_header_default = NyctHeader( 114 + version: "1.0", 115 + trip_replacement_periods: [], 116 + ) 117 + 118 + const trip_replacement_period_default = TripReplacementPeriod( 119 + route_id: "", 120 + replacement_period: 0, 121 + ) 119 122 120 123 fn feed_header_decoder() -> Decoder(FeedHeader) { 121 - use version <- decode.field(1, decode_string()) 122 - decode.success(FeedHeader(version:)) 124 + use gtfs_realtime_version <- decode.field(1, decoders.string()) 125 + use timestamp <- decode.field(3, decoders.fixed(64)) 126 + use nyct <- decode.field( 127 + 1001, 128 + decoders.protobuf( 129 + using: nyct_header_decoder, 130 + named: "NyctHeader", 131 + default: nyct_header_default, 132 + ), 133 + ) 134 + FeedHeader(gtfs_realtime_version:, timestamp:, nyct:) |> decode.success 135 + } 136 + 137 + fn nyct_header_decoder() -> Decoder(NyctHeader) { 138 + use version <- decode.field(1, decoders.string()) 139 + 140 + use trip_replacement_periods <- decode.field( 141 + 2, 142 + decode.list(of: decoders.protobuf( 143 + using: trip_replacement_period_decoder, 144 + named: "TripReplacementPeriod", 145 + default: trip_replacement_period_default, 146 + )), 147 + ) 148 + 149 + NyctHeader(version:, trip_replacement_periods:) |> decode.success 150 + } 151 + 152 + fn trip_replacement_period_decoder() -> Decoder(TripReplacementPeriod) { 153 + use route_id <- decode.field(1, decoders.string()) 154 + use replacement_period <- decode.field( 155 + 2, 156 + decoders.protobuf( 157 + using: trip_replacement_period_time_range_decoder, 158 + named: "TripReplacePeriod.TimeRange", 159 + default: 0, 160 + ), 161 + ) 162 + TripReplacementPeriod(route_id:, replacement_period:) |> decode.success 163 + } 164 + 165 + fn trip_replacement_period_time_range_decoder() -> Decoder(Int) { 166 + use time <- decode.field(2, decoders.fixed(64)) 167 + time |> decode.success 123 168 } 124 169 125 170 pub fn gtfs_test() { 126 171 let path = "./test/gtfs-short.pb" 127 172 let assert Ok(bits) = file.read_bits(from: path) 128 - let assert Ok(gtfs) = parse(from: bits, using: gtfs_decoder()) 173 + let assert Ok(header) = parse(from: bits, using: feed_header_decoder()) 129 174 130 - echo gtfs 131 - 132 - Nil 175 + assert header 176 + == Parsed( 177 + FeedHeader( 178 + "1.0", 179 + 1_758_224_061, 180 + NyctHeader("1.0", [ 181 + TripReplacementPeriod("7", 1_758_224_061), 182 + TripReplacementPeriod("6", 1_758_224_061), 183 + TripReplacementPeriod("5", 1_758_224_061), 184 + TripReplacementPeriod("2", 1_758_224_061), 185 + TripReplacementPeriod("1", 1_758_224_061), 186 + ]), 187 + ), 188 + <<>>, 189 + 102, 190 + ) 133 191 }