# -*- coding: utf-8 -*- """岗位与企业展示字段批次:把运行时 OpenAPI 的展示字段约束与文档模型同步进静态契约 ``docs/openapi-rc.json``。 本轮整改(对照整改项 2)后,本批相关字段的约束由**纯文档型注解**在运行时表达 (如 ``@Schema(types={"string","null"}, maxLength=40)``、``@ArraySchema(maxItems=20, schema=@Schema(types={"string"}, maxLength=40))``), 因此静态契约与运行时契约可以在**同一 OpenAPI 3.1 表示**下逐项对齐: * ``JobCreateRequest`` / ``JobUpdateRequest``:experience/education(≤40,可空)、 workLocation(≤255,可空)、benefits(数组、最多 20 项、单项 ≤40、元素为 string)、 salaryMonths(整数 1—24,可空),以及 salaryMin/salaryMax 的“元/月”说明。 * ``CompanyCreateRequest`` / ``CompanyUpdateRequest`` / ``CompanyPublicView`` 的 companySize(≤40,可空)。 * ``CompanyManagedJobItem`` 的展示字段。 * Map 型岗位响应的**文档模型**:``ApiResponseJobDetailViewDoc`` / ``ApiResponseJobListViewDoc`` / ``ApiResponseJobPublicViewDoc`` 及其 data(``JobDetailViewDoc``/``JobListViewDoc``/``JobPublicViewDoc``/``JobDocCompanySummary``)。 这 7 个组件整体从运行时同步,并把 ``GET /jobs``、``POST /jobs``、``PUT /jobs/{jobId}``、``GET /jobs/{jobId}``、 ``GET /jobs/public/{jobId}`` 的 200 响应引用与说明对齐。 不新增/删除端点(本批基线 paths=201 / operations=245 不变)。 用法: python docs/_update_job_company_display_fields_openapi.py [--runtime ] """ import argparse import json from pathlib import Path ROOT = Path(__file__).resolve().parent STATIC = ROOT / "openapi-rc.json" DEFAULT_RUNTIME = ROOT / "acceptance" / "batch-display-fields-20261016" / "runtime-openapi.json" DISPLAY_FIELDS = ("experience", "education", "benefits", "salaryMonths", "workLocation") SALARY_FIELDS = ("salaryMin", "salaryMax") JOB_REQUEST_SCHEMAS = ("JobCreateRequest", "JobUpdateRequest") COMPANY_SIZE_SCHEMAS = ("CompanyCreateRequest", "CompanyUpdateRequest", "CompanyPublicView") DOC_SCHEMAS = ("JobDocCompanySummary", "JobDetailViewDoc", "JobPublicViewDoc", "JobListViewDoc", "ApiResponseJobDetailViewDoc", "ApiResponseJobPublicViewDoc", "ApiResponseJobListViewDoc") MAP_RESPONSE_OPS = (("/jobs", "get", "ApiResponseJobListViewDoc"), ("/jobs", "post", "ApiResponseJobDetailViewDoc"), ("/jobs/{jobId}", "get", "ApiResponseJobDetailViewDoc"), ("/jobs/{jobId}", "put", "ApiResponseJobDetailViewDoc"), ("/jobs/public/{jobId}", "get", "ApiResponseJobPublicViewDoc")) def patch_display_props(static_schemas, runtime_schemas): for name in JOB_REQUEST_SCHEMAS: sp = static_schemas[name]["properties"] rp = runtime_schemas[name]["properties"] benefits = rp['benefits'] assert benefits.get('x-normalization') == dict(trimItems=True, deduplicate=True, rejectBlankItems=True, maxItems=20, itemMaxLength=40), '运行时福利语义错误,拒绝覆盖静态契约' assert 'maxItems' not in benefits and 'maxLength' not in benefits.get('items', {}), '拒绝写入原始请求限制' for field in DISPLAY_FIELDS + SALARY_FIELDS: sp[field] = rp[field] for name in COMPANY_SIZE_SCHEMAS: static_schemas[name]["properties"]["companySize"] = \ runtime_schemas[name]["properties"]["companySize"] mp = static_schemas["CompanyManagedJobItem"]["properties"] rmp = runtime_schemas["CompanyManagedJobItem"]["properties"] for field in DISPLAY_FIELDS + SALARY_FIELDS: mp[field] = rmp[field] def patch_doc_schemas(static_schemas, runtime_schemas): for name in DOC_SCHEMAS: assert name in runtime_schemas, "运行时缺少文档模型 %s" % name static_schemas[name] = runtime_schemas[name] def patch_response_refs(static_paths, runtime_paths): for path, method, schema in MAP_RESPONSE_OPS: static_resp = static_paths[path][method]["responses"]["200"] runtime_resp = runtime_paths[path][method]["responses"]["200"] static_resp["description"] = runtime_resp.get("description", static_resp.get("description", "OK")) static_resp["content"] = runtime_resp["content"] ref = runtime_resp["content"]["*/*"]["schema"].get("$ref", "") assert ref.endswith(schema), "%s %s 运行时响应引用异常:%s" % (method.upper(), path, ref) def main(): parser = argparse.ArgumentParser() parser.add_argument("--runtime", default=str(DEFAULT_RUNTIME)) args = parser.parse_args() doc = json.load(open(STATIC, encoding="utf-8")) runtime = json.load(open(args.runtime, encoding="utf-8")) static_schemas = doc["components"]["schemas"] runtime_schemas = runtime["components"]["schemas"] patch_display_props(static_schemas, runtime_schemas) patch_doc_schemas(static_schemas, runtime_schemas) patch_response_refs(doc["paths"], runtime["paths"]) with open(STATIC, "w", encoding="utf-8", newline="\n") as fh: json.dump(doc, fh, ensure_ascii=False, indent=2) fh.write("\n") print("display-fields OpenAPI updated: paths=", len(doc["paths"]), "operations=", sum(len(v) for v in doc["paths"].values()), "schemas=", len(static_schemas)) if __name__ == "__main__": main()