• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / storage / 22894890047

10 Mar 2026 09:03AM UTC coverage: 76.743% (+0.5%) from 76.206%
22894890047

Pull #875

github

web-flow
Merge b374168c5 into 53462ff99
Pull Request #875: feat: support unicode in object names

4230 of 5964 branches covered (70.93%)

Branch coverage included in aggregate %.

478 of 554 new or added lines in 23 files covered. (86.28%)

194 existing lines in 5 files now uncovered.

27273 of 35086 relevant lines covered (77.73%)

201.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.71
/src/http/plugins/xml.ts
1
import accepts from '@fastify/accepts'
2✔
2
import { FastifyInstance } from 'fastify'
2✔
3
import fastifyPlugin from 'fastify-plugin'
2✔
4
import xml from 'xml2js'
2✔
5

2✔
6
type XmlParserOptions = { disableContentParser?: boolean; parseAsArray?: string[] }
2✔
7
type RequestError = Error & { statusCode?: number }
2✔
8

2✔
9
export function decodeXmlNumericEntities(value: string): string {
4✔
10
  const isValidXmlCodePoint = (codePoint: number) => {
4✔
11
    if (!Number.isInteger(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
20✔
12
      return false
2✔
13
    }
2✔
14

18✔
15
    return (
18✔
16
      codePoint === 0x9 ||
18✔
17
      codePoint === 0xa ||
20✔
18
      codePoint === 0xd ||
20✔
19
      (codePoint >= 0x20 && codePoint <= 0xd7ff) ||
20✔
20
      (codePoint >= 0xe000 && codePoint <= 0xfffd) ||
20✔
21
      (codePoint >= 0x10000 && codePoint <= 0x10ffff)
18✔
22
    )
20✔
23
  }
20✔
24

4✔
25
  return value.replace(
4✔
26
    /&#([xX][0-9a-fA-F]{1,6}|[0-9]{1,7});/g,
4✔
27
    (match: string, rawValue: string) => {
4✔
28
      const isHex = rawValue[0].toLowerCase() === 'x'
20✔
29
      const codePoint = Number.parseInt(isHex ? rawValue.slice(1) : rawValue, isHex ? 16 : 10)
20✔
30
      if (!isValidXmlCodePoint(codePoint)) {
20✔
31
        return match
16✔
32
      }
16✔
33

4✔
34
      return String.fromCodePoint(codePoint)
4✔
35
    }
4✔
36
  )
4✔
37
}
4✔
38

2✔
39
function forcePathAsArray(node: unknown, pathSegments: string[]): void {
4✔
40
  if (pathSegments.length === 0 || node === null || node === undefined) {
4!
UNCOV
41
    return
×
UNCOV
42
  }
×
43

4✔
44
  if (Array.isArray(node)) {
4!
UNCOV
45
    node.forEach((item) => forcePathAsArray(item, pathSegments))
×
UNCOV
46
    return
×
47
  }
×
48

4✔
49
  if (typeof node !== 'object') {
4!
UNCOV
50
    return
×
UNCOV
51
  }
×
52

4✔
53
  const [current, ...rest] = pathSegments
4✔
54
  const currentRecord = node as Record<string, unknown>
4✔
55

4✔
56
  if (!(current in currentRecord)) {
4!
UNCOV
57
    return
×
UNCOV
58
  }
×
59

4✔
60
  if (rest.length === 0) {
4✔
61
    const value = currentRecord[current]
2✔
62
    if (value !== undefined && !Array.isArray(value)) {
2✔
63
      currentRecord[current] = [value]
2✔
64
    }
2✔
65
    return
2✔
66
  }
2✔
67

2✔
68
  forcePathAsArray(currentRecord[current], rest)
2✔
69
}
2✔
70

2✔
71
export const xmlParser = fastifyPlugin(
2✔
72
  async function (fastify: FastifyInstance, opts: XmlParserOptions) {
2✔
73
    fastify.register(accepts)
3,322✔
74

3,322✔
75
    if (!opts.disableContentParser) {
3,322✔
76
      fastify.addContentTypeParser(
3,020✔
77
        ['text/xml', 'application/xml'],
3,020✔
78
        { parseAs: 'string' },
3,020✔
79
        (_request, body, done) => {
3,020✔
80
          if (!body) {
4!
UNCOV
81
            done(null, null)
×
UNCOV
82
            return
×
83
          }
×
84

4✔
85
          xml.parseString(
4✔
86
            body,
4✔
87
            {
4✔
88
              explicitArray: false,
4✔
89
              trim: true,
4✔
90
              valueProcessors: [
4✔
91
                decodeXmlNumericEntities,
4✔
92
                xml.processors.parseNumbers,
4✔
93
                xml.processors.parseBooleans,
4✔
94
              ],
4✔
95
            },
4✔
96
            (err: Error | null, parsed: unknown) => {
4✔
97
              if (err) {
4✔
98
                const parseError: RequestError = new Error(`Invalid XML payload: ${err.message}`)
2✔
99
                parseError.statusCode = 400
2✔
100
                done(parseError)
2✔
101
                return
2✔
102
              }
2✔
103

2✔
104
              if (parsed && opts.parseAsArray?.length) {
4✔
105
                opts.parseAsArray.forEach((path) => {
2✔
106
                  if (!path) {
2!
UNCOV
107
                    return
×
UNCOV
108
                  }
×
109
                  forcePathAsArray(parsed, path.split('.'))
2✔
110
                })
2✔
111
              }
2✔
112

2✔
113
              done(null, parsed)
2✔
114
            }
2✔
115
          )
4✔
116
        }
4✔
117
      )
3,020✔
118
    }
3,020✔
119

3,322✔
120
    fastify.addHook('preSerialization', async (req, res, payload) => {
3,322✔
121
      const accept = req.accepts()
2✔
122

2✔
123
      const acceptedTypes = ['application/xml', 'text/html']
2✔
124

2✔
125
      if (acceptedTypes.some((allowed) => accept.types(acceptedTypes) === allowed)) {
2✔
126
        res.serializer((payload) => payload)
2✔
127

2✔
128
        const xmlBuilder = new xml.Builder({
2✔
129
          renderOpts: {
2✔
130
            pretty: false,
2✔
131
          },
2✔
132
        })
2✔
133
        const xmlPayload = xmlBuilder.buildObject(payload)
2✔
134
        res.type('application/xml')
2✔
135
        res.header('content-type', 'application/xml; charset=utf-8')
2✔
136
        return xmlPayload
2✔
137
      }
2✔
UNCOV
138

×
UNCOV
139
      return payload
×
140
    })
3,322✔
141
  },
3,322✔
142
  { name: 'xml-parser' }
2✔
143
)
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc