feat(meta-decompile): поддержка HTTPService + починка его компиляции

meta-decompile не знал тип HTTPService, поэтому 7 сервисов УТ не проходили
раундтрип вовсе. Добавлен разбор: rootURL, reuseSessions, sessionMaxAge и
дерево urlTemplates -> methods.

Раундтрип на реальных сервисах вскрыл три пробела компилятора:

1. ReuseSessions: в allowlist были только DontUse и AutoUse, а платформа
   пишет ещё и Use (4 объекта в корпусе) — компиляция падала с ошибкой.
2. Обработчик метода выводился по формуле ИмяШаблона+ИмяМетода; в реальных
   конфигурациях он произвольный (УдаленныйВызовМетодаЧерезТелоЗапроса).
   Метод получил объектную форму {httpMethod, handler, synonym, comment}
   рядом со строчным сокращением "только HTTP-метод".
3. Comment не эмитился ни у объекта, ни у шаблонов и методов, хотя платформа
   пишет тег всегда.

Порядок шаблонов и методов приведён к порядку DSL в обоих портах: PS шёл в
порядке хеш-таблицы, py сортировал — снэпшоты бы разъехались между портами.

В декомпиляторе сравнение синонима с авто-выводом сделано регистрочувствительным
(-ceq): "Post" против "post" считалось совпадением, и синоним терялся.

Проверка: HTTP-сервисы УТ 7/7 без расхождений (было 0 — тип не поддерживался),
сюита 76/76 на обоих портах, 1С-сертификация кейса пройдена.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-01 21:20:44 +03:00
co-authored by Claude Opus 5
parent dac265995e
commit 01c45581ef
6 changed files with 170 additions and 36 deletions
@@ -1,4 +1,4 @@
# meta-compile v1.70 — Compile 1C metadata object from JSON
# meta-compile v1.71 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -267,7 +267,7 @@ $script:validEnumValues = @{
"RegisterRecordsDeletion" = @("AutoDelete","AutoDeleteOnUnpost","AutoDeleteOff")
"RegisterRecordsWritingOnPost" = @("WriteModified","WriteSelected","WriteAll")
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
"ReuseSessions" = @("DontUse","AutoUse")
"ReuseSessions" = @("DontUse","Use","AutoUse")
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
"SubordinationUse" = @("ToItems","ToFolders","ToFoldersAndItems")
@@ -3800,7 +3800,7 @@ function Emit-HTTPServiceProperties {
X "$i<Name>$(Esc-Xml $objName)</Name>"
Emit-MLText $i "Synonym" $synonym
X "$i<Comment/>"
if ($def.comment) { X "$i<Comment>$(Esc-Xml "$($def.comment)")</Comment>" } else { X "$i<Comment/>" }
$rootURL = if ($def.rootURL) { "$($def.rootURL)" } else { $objName.ToLower() }
X "$i<RootURL>$(Esc-Xml $rootURL)</RootURL>"
@@ -3881,15 +3881,20 @@ function Emit-URLTemplate {
$tmplSynonym = Split-CamelCase $tmplName
$template = ""
$methods = @{}
# [ordered], а не @{}: порядок методов должен совпадать с DSL (он же порядок исходного XML),
# иначе PS и py расходятся между собой и с выгрузкой платформы.
$methods = [ordered]@{}
$tmplComment = ""
if ($tmplDef -is [string]) {
$template = "$tmplDef"
} else {
$template = if ($tmplDef.template) { "$($tmplDef.template)" } else { "/$($tmplName.ToLower())" }
if ($tmplDef.synonym) { $tmplSynonym = "$($tmplDef.synonym)" }
if ($tmplDef.comment) { $tmplComment = "$($tmplDef.comment)" }
if ($tmplDef.methods) {
$tmplDef.methods.PSObject.Properties | ForEach-Object {
$methods[$_.Name] = "$($_.Value)"
$methods[$_.Name] = $_.Value # строка (HTTP-метод) ЛИБО объект {httpMethod, handler, synonym, comment}
}
}
}
@@ -3898,6 +3903,7 @@ function Emit-URLTemplate {
X "$indent`t<Properties>"
X "$indent`t`t<Name>$(Esc-Xml $tmplName)</Name>"
Emit-MLText "$indent`t`t" "Synonym" $tmplSynonym
if ($tmplComment) { X "$indent`t`t<Comment>$(Esc-Xml $tmplComment)</Comment>" } else { X "$indent`t`t<Comment/>" }
X "$indent`t`t<Template>$(Esc-Xml $template)</Template>"
X "$indent`t</Properties>"
@@ -3905,14 +3911,25 @@ function Emit-URLTemplate {
X "$indent`t<ChildObjects>"
foreach ($methodName in $methods.Keys) {
$methodUuid = New-Guid-String
$httpMethod = $methods[$methodName]
$methodSynonym = Split-CamelCase $methodName
$handler = "${tmplName}${methodName}"
$mDef = $methods[$methodName]
# Строка — сокращение "только HTTP-метод"; объект — полная форма. Обработчик по умолчанию
# выводится как ИмяШаблона+ИмяМетода, но в реальных конфигурациях он произвольный,
# поэтому задаётся явно ключом handler.
if ($mDef -is [string]) {
$httpMethod = "$mDef"; $handler = "${tmplName}${methodName}"
$methodSynonym = Split-CamelCase $methodName; $methodComment = ""
} else {
$httpMethod = if ($mDef.httpMethod) { "$($mDef.httpMethod)" } else { 'GET' }
$handler = if ($mDef.handler) { "$($mDef.handler)" } else { "${tmplName}${methodName}" }
$methodSynonym = if ($mDef.synonym) { "$($mDef.synonym)" } else { Split-CamelCase $methodName }
$methodComment = if ($mDef.comment) { "$($mDef.comment)" } else { "" }
}
X "$indent`t`t<Method uuid=`"$methodUuid`">"
X "$indent`t`t`t<Properties>"
X "$indent`t`t`t`t<Name>$(Esc-Xml $methodName)</Name>"
Emit-MLText "$indent`t`t`t`t" "Synonym" $methodSynonym
if ($methodComment) { X "$indent`t`t`t`t<Comment>$(Esc-Xml $methodComment)</Comment>" } else { X "$indent`t`t`t`t<Comment/>" }
X "$indent`t`t`t`t<HTTPMethod>$httpMethod</HTTPMethod>"
X "$indent`t`t`t`t<Handler>$(Esc-Xml $handler)</Handler>"
X "$indent`t`t`t</Properties>"
@@ -4369,7 +4386,9 @@ if ($objType -in @("FilterCriterion", "SettingsStorage")) {
# --- HTTPService: URLTemplates ---
if ($objType -eq "HTTPService") {
$urlTemplates = @{}
# [ordered]: порядок шаблонов — как в DSL (он же порядок исходного XML). @{} давало произвольный
# порядок в PS и расходилось с py, который сортировал; обе ветки приведены к порядку DSL.
$urlTemplates = [ordered]@{}
if ($def.urlTemplates) {
$def.urlTemplates.PSObject.Properties | ForEach-Object {
$urlTemplates[$_.Name] = $_.Value
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.70 — Compile 1C metadata object from JSON
# meta-compile v1.71 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -416,7 +416,7 @@ valid_enum_values = {
'RegisterRecordsDeletion': ['AutoDelete', 'AutoDeleteOnUnpost', 'AutoDeleteOff'],
'RegisterRecordsWritingOnPost': ['WriteModified', 'WriteSelected', 'WriteAll'],
'ReturnValuesReuse': ['DontUse', 'DuringRequest', 'DuringSession'],
'ReuseSessions': ['DontUse', 'AutoUse'],
'ReuseSessions': ['DontUse', 'Use', 'AutoUse'],
'FillChecking': ['DontCheck', 'ShowError', 'ShowWarning'],
'Indexing': ['DontIndex', 'Index', 'IndexWithAdditionalOrder'],
'SubordinationUse': ['ToItems', 'ToFolders', 'ToFoldersAndItems'],
@@ -3729,7 +3729,7 @@ def emit_http_service_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<Comment>{esc_xml(str(defn["comment"]))}</Comment>' if defn.get('comment') else f'{i}<Comment/>')
root_url = str(defn['rootURL']) if defn.get('rootURL') else obj_name.lower()
X(f'{i}<RootURL>{esc_xml(root_url)}</RootURL>')
reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse')
@@ -3800,29 +3800,48 @@ def emit_url_template(indent, tmpl_name, tmpl_def):
tmpl_synonym = split_camel_case(tmpl_name)
template = ''
methods = {}
tmpl_comment = ''
if isinstance(tmpl_def, str):
template = tmpl_def
else:
template = str(tmpl_def['template']) if tmpl_def.get('template') else f'/{tmpl_name.lower()}'
if tmpl_def.get('synonym'):
tmpl_synonym = str(tmpl_def['synonym'])
if tmpl_def.get('comment'):
tmpl_comment = str(tmpl_def['comment'])
if tmpl_def.get('methods'):
for k, v in tmpl_def['methods'].items():
methods[k] = str(v)
methods[k] = v # строка (HTTP-метод) ЛИБО объект {httpMethod, handler, synonym, comment}
X(f'{indent}<URLTemplate uuid="{uid}">')
X(f'{indent}\t<Properties>')
X(f'{indent}\t\t<Name>{esc_xml(tmpl_name)}</Name>')
emit_mltext(f'{indent}\t\t', 'Synonym', tmpl_synonym)
X(f'{indent}\t\t<Comment>{esc_xml(tmpl_comment)}</Comment>' if tmpl_comment else f'{indent}\t\t<Comment/>')
X(f'{indent}\t\t<Template>{esc_xml(template)}</Template>')
X(f'{indent}\t</Properties>')
if methods:
X(f'{indent}\t<ChildObjects>')
for method_name, http_method in sorted(methods.items()):
# Порядок — как в DSL (он же порядок исходного XML), а не отсортированный: иначе
# порты расходятся между собой и с выгрузкой платформы.
for method_name, m_def in methods.items():
method_uuid = new_uuid()
method_synonym = split_camel_case(method_name)
handler = f'{tmpl_name}{method_name}'
# Строка — сокращение "только HTTP-метод"; объект — полная форма. Обработчик по
# умолчанию ИмяШаблона+ИмяМетода, но в реальных конфигурациях он произвольный.
if isinstance(m_def, str):
http_method = m_def
handler = f'{tmpl_name}{method_name}'
method_synonym = split_camel_case(method_name)
method_comment = ''
else:
http_method = str(m_def.get('httpMethod') or 'GET')
handler = str(m_def.get('handler') or f'{tmpl_name}{method_name}')
method_synonym = str(m_def['synonym']) if m_def.get('synonym') else split_camel_case(method_name)
method_comment = str(m_def.get('comment') or '')
X(f'{indent}\t\t<Method uuid="{method_uuid}">')
X(f'{indent}\t\t\t<Properties>')
X(f'{indent}\t\t\t\t<Name>{esc_xml(method_name)}</Name>')
emit_mltext(f'{indent}\t\t\t\t', 'Synonym', method_synonym)
X(f'{indent}\t\t\t\t<Comment>{esc_xml(method_comment)}</Comment>' if method_comment else f'{indent}\t\t\t\t<Comment/>')
X(f'{indent}\t\t\t\t<HTTPMethod>{http_method}</HTTPMethod>')
X(f'{indent}\t\t\t\t<Handler>{esc_xml(handler)}</Handler>')
X(f'{indent}\t\t\t</Properties>')
@@ -4260,7 +4279,8 @@ if obj_type == 'HTTPService':
if url_templates:
has_children = True
X('\t\t<ChildObjects>')
for tmpl_name in sorted(url_tmpl_order):
# Порядок — как в DSL (он же порядок исходного XML), а не отсортированный.
for tmpl_name in url_tmpl_order:
emit_url_template('\t\t\t', tmpl_name, url_templates[tmpl_name])
X('\t\t</ChildObjects>')
else: